그렇다고 해서 DB 커넥션을 제공해주지 않으면 DAO가 동작하지 않을테니 CountingConnectionMaker가 다시 실제 사용할 DB 커넥션을 제공해주는 DConnectionMaker를 호출하도록 만들어야 한다.
역시 DI를 사용하면 된다. 이렇게 해서 재구성된 새로운 런타임 의존관계가 만들어진다.
새로운 의존관계를 컨테이너가 사용할 설정정보를 이용해 만든다.
CountingDaoFactory라는 이름의 설정용 클래스를 만든다.
기존의 DaoFactory와 달리 ConnectionMaker() 메서드에서 CountingConnectionMaker 타입 오브젝트를 생성하도록 만든다.
그리고 실제 DB 커넥션을 만들어주는 DConnectionMaker는 이름이 realConnectionMaker()인 메서드에서 생성하게 한다.
그리고 realConnectionMaker() 메서드가 만들어주는 오브젝트는 connectionMaker()에서 만드는 오브젝트의 생성자를 통해 DI해준다.
기존 DAO설정부분은 바꾸지 않아도 된다.
계속해서 connectionMaker() 메서드를 통해 생성되는 오브젝트를 사용하게 한다.
@Configuration
public class CountingDaoFactory {
@Bean
public UserDao userDao() {
return new UserDao(connectionMaker()); //모든 DAO는 여전히 connectionMaker()에서 만들어지는 오브젝트를 DI를 받는다.
}
@Bean
public ConnectionMaker connectionMaker() {
return new CountingConnectionMaker(realConnectionMaker());
}
@Bean
public ConnectionMaker realConnectionMaker() {
return new DConnectionMaker();
}
}
이제 커넥션 카운팅을 위한 실행 코드를 만든다.
기본적으로는 UserDaoTest와 같지만 설정용 클래스를 CountingDaoFactory로 변경해줘야 한다.
DAO를 DL방식으로 가져와 어떤 작업이든 여러 번 실행시킨다.
그리고 CountingConnectionMaker 빈을 가져온다.
설정정보에 지정된 이름과 타입만 알면 특정 빈을 가져올 수 있으니 CountingConnectionMaker 오브젝트를 가져오는 건 간단하다.
CountingConnectionMaker에는 그동안 DAO를 통해 DB 커넥션을 요청한 횟수만큼 카운터가 증개해 있어야 한다.
카운터 값을 가져와서 화면에 출력해보고 DAO의 사용횟수와 일치하는지 확인해보자.
CountingConnectionMaker 테스트용 클래스
public class UserDaoConnectionCountingTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
AnnotaionConfigApplicationContext context = new AnnontationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", userDao.class);
//Dao사용코드
for(int i=0; i<10; i++) {
User user = new User();
user.setId(""+i);
user.setName(""+i);
user.setPassword(""+i);
dao.add(user);
}
CountingConnectionMaker ccm = context.getBean("connectionMaker",CountingConnectionMaker.class);
System.out.println("Connection counter : " + ccm.getCounter());
}
}
지금은 DAO가 하나뿐이지만 DAO가 수십, 수백개여도 상관없다.
DI의 장점은 관심사의 분리(SoC)를 통해 얻어지는 높은 응집도에서 나온다.
모든 DAO가 직접 의존해서 사용할 ConnectionMaker 타입 오브젝트는 connectionMaker() 메서드에서 만든다.
따라서 CountingConnectionMaker의 의존관계를 추가하려면 이 메서드만 수정하면 그만이다.
또한 CountingConnectionMaker를 이용한 분석 작업이 모두 끝나면 다시 CountingDaoFactory 설정 클래스를 DaoFactory로 변경하거나 connectionMaker() 메서드를 수정하는 것만으로 DAO의 런타임 의존관계는 이전 상태로 복구된다.
정말 멋지고 편리하지 않는가?
바론 이런 것이 의존관계 주입의 매력을 잘 드러내는 응용 방법이다.
DI의 활용하는 방법은 매우 다양하다.
다시 말하지만 스프링이 제공하는 대부분의 기능은 DI없이는 존재할수도 없은 것들이다.
스프링은 DI를 쳔하게 사용할 수 있도록 도와주는 도구이면서 그 자체로 DI를 적극 활용한 프레임워크이기도 하다.
DI가 정말 중요하다고 좋다고 생각한다면 자기 스스로 먼저 적용하지 않을 이유가 없다.
그래서 스프링을 공부하는 건 DI를 어떻게 활용해야 할지를 공부하는 것이기도 하다.
메서드를 이용한 의존관계주입
UserDao의 의존관계 주입을 위해 생성자를 사용했다.
생성자에 파라미터를 만들어두고 이를 통해 DI 컨테이너가 의존할 오브젝트 레퍼런스를 넘겨주도록 만들었다.
그런데 의존관계 주입시 반드시 생성자를 사용해야 하는 것은 아니다.
생성자가 아닌 일반 메서드를 사용할수도 있을 뿐만 아니라, 생성자를 사용하는 방법보다 더 자주 사용된다.
생성자가 아닌 일반 메서드를 이용해 의존 오브젝트와의 관계를 주입해주는데 크게 두가지 방법이 있다.
1. 수정자 메서드를 이용한 주입
수정자(setter) 메서드는 외부에서 오브젝트 내부의 애트리뷰트 값을 변경하려는 용도로 주로 사용된다.
메서드는 항상 set으로 시작한다.
간단히 수정자라고 불리기도 하다.
수정자 메서드의 핵심기능은 파라미터로 전달된 값을 보통 내부의 인스턴스 변수에 저장하는 것이다.
부자걱으로, 입력값에 대한 검증이나 그 밖의 작업을 수행할 수도 있다.
수정자 메서드는 외부로부터 제공받은 오브젝트 레퍼런스를 저장해뒀다가 내부의 메서드에서 사용하게 하는 DI 방식에서 활용하기 적당하다.
2. 일반메서드를 이용한 주입
수정자 메서드처럼 set으로 시작해야하고 한번에 한개의 파라미터만 가질수 있다는 제약이 싫다면 여러개의 파라미터를 갖는 일반 메서드를 DI용으로 사용할 수도 있다.
생성자가 수정자 메서드보다 나은점은 한번에 여러 개의 파라미터를 받을 수 있다는 점이다.
하지만 파라미터의 개수가 많아지고 비슷한 타입이 여러 개라면 실수하기 쉽다.
임의의 초기화 메서드를 이용하는 DI는 적절한 개수의 파라미터를 가진 여러 개의 초기화 메서드를 만들 수도 있기 때문에 한번에 모든 필요한 파라미터를 다 받아야 하는 생성자보다 낫다.
스프링은 전통적으로 메서드를 이용한 DI방법 중에서 수정자 메서드를 가장 많이 사용해왔다.
DaoFactory와 같은 자바코드 대신 XML을 사용하는 경우에는 자바빈 규약을 따르는 수정자 메서드가 가장 사용하기 편리하다.
수정자 메서드 DI를 사용할때는 메서드의 이름을 잘 결정하게 중요하다.
가능한 의미있고 단순한 이름을 사용하자. 이름을 짓는게 귀찮다면 메서드를 통해 DI 받을 오브젝트의 타입 이름을 따르는 것이 가장 무난하다.
예를 들어 ConnectionMaker 인터페이스 타입의 오브젝트를 DI 받는다면 메서드의 이름은 setConnectionMaker()라고 하는 것이다.
특별한 이름을 지정해서 의미를 더 부여해줄 생각이 아니
라면 이 관계를 따르도록 한다.
UserDao도 수정자 메서드를 이용해 DI 하도록 만들어보자.
기존 생성자는 제거한다.
생성자를 대신할 setConnectionMaker()라는 메서드를 하나 추가하고 파라미터로 ConnectionMaker 타입의 오브젝트를 받도록 선언한다.
파라미터로 받은 오브젝트는 인스턴스 변수에 저장해두도록 만든다.
대부분의 IDE는 수정자 메서드를 자동생성하는 기능이 있다.
인스턴스 변수만 정의해두고 자동생성 기능을 사용하면 편리하다.
public class UserDao {
private ConnectionMaker connectionMaker;
public void setConnectionMaker(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
//수정자 메서드의 DI의 전형적인 코드다. setter메서드는 보통 IDE의 자동생성기능을 사용해서 만드는 것이 편리하다.
}
UserDao를 수정자 메서드 DI방식이 가능하다록 변경했으니 DI를 적용하는 DaoFactory의 코드도 함께 수정해줘야 한다.
수정자 메서드 DI를 사용해 UserDao 타입의 빈을 만드는 DaoFactory의 UserDao() 메서드
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.setConnectionMaker(connectionMaker());
return userDao;
}
단지 의존관계를 주입하는 시점과 방법이 달라졌을 뿐 결과는 동일하다.
UserDaoTest를 실행서 개선한 코드의 기능에 문제가 없는지 확인해보자.
실제로 스프링은 생성자, 수정자 메서드, 초기화 메서드를 이용한 방법 외에도 다양한 의존관계 주입 방법을 지원한다.