3장에서는 스프링에 적용된 다양한 템플릿을 살펴본다.
템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법이다.
코드에서 발생하는 예외를 따로 처리해주지 않는다면 프로그램은 종료하게된다. 다중 사용자를 위한 서버라면 큰 문제를 발생시킬 수도 있다. 따라서 try/catch/finally를 통해 예외처리를 해주어야한다.
특히 JDBC에서는 제한된 개수의 DB 커넥션을 만들어 재사용 가능한 풀(pool)로 관리한다. Connection과 PreparedStatement 등 공유 리소스를 사용하는 경우, close() 메소드를 통해 사용이 끝난 리소스를 빠르게 반환해주어야 하는데 그렇지 않으면 풀에 있는 리소스가 고갈되게 되고, 에러를 발생하게 된다.
더 효율적으로 관리하기 위해서는 변하는 부분과 변하지 않는 부분을 구분하는 것이다. 변하는 부분을 전략 패턴을 통해 의존관계를 주입해보자.
JDBC의 메소드에서 변하는 부분 중 하나로 PreparedStatement가 있다. 이러한 변하는 부분에 대한 인터페이스를 선언하고 생성 전략을 생성해 의존관계를 주입해줄 수 있다.
StatementStrategy 인터페이스
public interface StatementStrategy {
PreparedStatement makePreparedStatement(Connection C) throws SQLException;
}
DeleteAllStrategy.java
public class DeleteAllStrategy implements StatementStrategy {
public PreparedStatement makePreparedStatement(Connection C)
throws SQLException {
PreparedStatement ps = c.preparedStatement("delete from users");
return ps
}
}
deleteAll() 메소드
public void deleteAll(StatementStrategy stsg) throws SQLException {
```
try {
c = dataSource.getConnection();
ps = stsg.makeConnection(c);
} catch (SQLException e) {
```
}
```
}
UserDaoClient.java
public class UserDaoClient {
private UserDao userDao;
private StatementStrategy stsg;
public UserDaoClient(UserDao userDao) {
this.userDao = userDao;
}
stsg = new DeleteAllStrategy();
userDao.deleteAll(stsg);
}
내가 이해한 방식으로 만든 전략 패턴이다.
이 방식에서는 두 가지 개선할 점이 있다.
이럴 경우에는 로컬 클래스를 통해 개선할 수 있다.
중첩 클래스 (nested class),
클래스 내부에 정의되는 클래스를 중첩 클래스라고 부른다. 그리고 중첩 클래스는
- 스태틱 클래스, 독립적인 오브젝트를 생성가능
- 내부 클래스, 선언된 클래스 내부에서만 사용할 수 있음
두 가지로 나뉘고, 내부 클래스는- 오브젝트 레벨에서 정의되는 멤버 내부 클래스
- 메소드 레벨에서 정의되는 로컬 클래스
- 이름을 갖지 않는 익명 내부 클래스
로 구분된다.
일정한 작업 패턴이 존재하고 그중 일부만 자주 바꿔서 사용해야하는 경우에는
익명 내부 클래스를 구현하는 인터페이스를 생성자처럼 이용하는 오브젝트를 만들면 된다.
UserDaoClient.java
public class UserDaoClient {
private UserDao userDao;
private StatementStrategy stsg;
public UserDaoClient(UserDao userDao) {
this.userDao = userDao;
}
stsg = new DeleteAllStrategy();
userDao.deleteAll(new StatementStrategy(
public PreparedStatement makePreparedStatement(Connection C) throws SQLException {
PreparedStatement ps = c.preparedStatement("delete from users");
return ps;
})
);
}
이러헥 메소드 파라미터로서 익명 내부 클래스를 구현할 수 있다.
DI를 해야하는 조건은 무엇일까?
두 가지로 말할 수 있다.
1. 싱글톤으로 관리될 필요가 있는 경우
2. 다른 빈에 의존하고 있는 경우
두 가지를 포괄하는 말로는 "스프링의 제어권에 의해 IoC의 대상이 될 경우"이다.
위처럼 바뀌지 않는 일정한 패턴을 갖는 작업의 흐름이 존재하고 그중 일부분만 자주 바꿔서 사용해야 하는 경우에는 전략 패턴의 기본 구조에 익명 내부 클래스를 활용하는 방식을 사용하는게 좋다. 이런 방식을 스프링에서는
템플릿/콜백 패턴 이라고 부른다.
어떤 목적을 위해 미리 만들어둔 모양이 있는 틀. 프로그래밍에서는 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우를 템플릿이라고 부른다.
콜백은 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트. 특정 로직을 담은 메소드를 실행시키기 위해 사용. 자바에서는 메소드 자체를 파라미터로 전달할 수는 없기 때문에 메소드가 담긴 오브젝트를 전달한다. 그래서 Functional object 라고 부르기도 한다.
콜백은 템플릿 안에서 호출되기 위한 목적으로 만들어진 오브젝트이다.