지금까지 UserDao를 리팩토링하는 작업을 했지만, UserDaoTest는 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 기능까지 맡게 되었다.
UserDaoTest는 원래 관심사였던 UserDao의 기능 테스트외에 다른 책임을 맡게 되었으니 UserDao와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 기능과, 그렇게 만들어진 두 개의 오브젝트가 연결되어서 사용될 수 있도록 관계를 맺어주는 기능을 분리해보자.
팩토리
팩토리는 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 오브젝트를 말한다.
DaoFactory
public class DaoFactory {
public UserDao userDao() {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao
}
}
팩토리를 사용하도록 UserDaoTest를 수정해보면
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
UserDao dao = new DaoFactory().userDao();
// ...
}
}
이렇게 되고, 이리하여 UserDaoTest에서 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 기능을 DaoFactory를 통해 분리하였다.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(new DConnectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(new DConnectionMaker());
}
public MessageDao messageDao() {
return new MessageDao(new DConnectionMaker());
}
}
하지만 만약 위와 같이 DaoFactory에서 UserDao만이 아니라 AccountDao, MessageDao등 여러 DAO를 생성한다면 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 기능이 중복돼서 나타난다.
public class DaoFactory {
public UserDao userDao() {
return new UserDao(connectionMaker());
}
public AccountDao accountDao() {
return new AccountDao(connectionMaker());
}
public MessageDao messageDao() {
return new MessageDao(connectionMaker());
}
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
위와 같이 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 기능을 분리하면 connectionMaker만 바꾸면 일일히 수정할 필요가 없다.
UserDao의 초기 버전을 보면, 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지를 스스로 결정한다. 즉 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조이다.
제어의 역전이란 이런 제어 흐름의 개념을 뒤집는 것이다.
제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지도, 생성하지도 않는다. 모든 제어 권한을 다른 대상에게 위임하기 때문이다.
프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다.
라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 반면에 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.
보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다.
스프링에서는 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트를 빈(bean)이라고 부른다. 동시에 스프링 빈은 스프링 컨테이너가 생성과 관계설정, 사용 등을 제어해주는 제어의 역전이 적용된 오브젝트를 가리키는 말이다.
스프링에서는 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리(Bean Factory)라고 부른다. 보통은 빈 팩토리보다는 이를 좀더 확장한 애플리케이션 컨텍스트를 주로 사용한다. (그냥 빈 팩토리 = 애플리케이션 컨텍스트라고 생각해도된다.)
애플리케이션 컨텍스트는 별도의 설정정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄한다. 즉 애플리케이션 컨텍스트는 어떤 클래스의 오브젝트를 생성하고 어디서 사용하도록 연결해줄 것인가 등의 설정정보를 담고있는 무언가를 가져와서 활용하는 범용적인 IoC엔진 같은 것이라고 볼 수 있다.
→ 애플리케이션은 애플리케이션 컨텍스트와 그 설정정보를 따라서 만들어지고 구성된다.
스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식할 수 있도록 @Configuration이라는 어노테이션을 추가하고, 오브젝트를 만들어주는 메소드에는 @Bean이라는 어노테이션을 붙여준다.
@Configuration // 애플리케이션 컨텍스트 or 빈 팩토리가 사용할 설정정보라는 표시
public class DaoFactory {
@Bean // 오브젝트 생성을 담당하는 IoC용 메소드라는 표시
public UserDao userDao() {
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new DConnectionMaker();
}
}
이 두가지 어노테이션을 통해 DaoFactory를 빈 팩토리가 사용할 설정정보를 담은 모습니다.
아래는 새로 만든 DaoFactory 어플리케이션 컨텍스트를 적용한 UserDaoTest이다.
public class UserDaoTest (
public static void main(String[) args) throws ClassNotFoundException,SQLException (
ApplicationContext context =
new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
// ...
}
getBean() 메소드는 ApplicationContext가 관리하는 오브젝트를 요청히는 메소드 다. getBean()의 파라미터인 "userDao"는 ApplicationContext에 등록된 빈의 이름이다. userDao라는 이름의 빈을 가져온다는 것 은 DaoFactory의 userDao() 메소드를 호출해서 그 결과를 가져온다고 생각하면 된다.
스프링에서는 애플리케이션 컨텍스트를 IoC 컨테이너 또는 스프링 컨테이너라고 부른다.
애플리케이션 컨택스트는 애플리케이션에서 IoC를 적용해서 관리할 모든 오브젝트에 대한 생성과 관계설정을 담당한다. 대신 ApplicationContext에는 직접 오브젝트를 생성하고 관계를 맺어주는 코드가 없고, 그런 생성정보와 연관관계 정보를 별도의 설정정보를 통해 얻는다.
@Configuration이 붙은 DaoFactory는 이 애플리케이션 컨텍스트가 활용하는 IoC 설정정보다. 내부적으로는 애플리케이션 컨텍스트가 DaoFactory의 userDao( ) 메소드 를 호출해서 오브젝트를 가져온 것을 클라이언트가 getBean()으로 요청할 때 전달해준다.
이와 같은 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점은 다음과 같다.