1.7.1 제어의 역전(IoC)와 의존관계 주입
IoC는 매우 폭 넓게 사용되는 용어이므로, 스프링이 제공하는 IoC 방식은 의존관계 주입(Dependency Injection)이라는 용어를 사용한다.
1.7.2 런타임 의존관계 설정
의존관계
A가 B에 의존한다.
- B가 변하면 A에 영향을 미친다.
- UML에서 점선 화살표로 표시함 (A ---> B)
- 방향성을 가지고 있으므로 반대는 성립하지 않는다. 즉, A가 변해도 B는 영향을 받지 않는다.
UserDao의 의존관계
UserDao ---> ConnectionMaker
- ConnectionMaker 인터페이스가 변하면 UserDao는 변해야한다.
- 그러나, ConnectionMaker를 구현한 DConnectionMaker에는 의존하지 않으므로 DConnectionMaker의 변화는 UserDao에 영향을 미치지 않는다.
런타임 의존관계 / 오브젝트 의존관계
- 설계 시점의 의존관계가 실체화 된 것
- 모델링 시점의 의존관계와는 성격이 다르다.
- UserDao 오브젝트와 DConnectionMaker 오브젝트의 관계
- 프로그램 시작 전에는 알 수가 없다.
의존관계 주입의 충족 조건
- 클래스 모델 또는 코드에 런타임 시의 의존관계가 드러나지 않는다. (이를 위해 인터페이스에만 의존하고 있어야 한다.)
- 런타임 시의 의존관계는 컨테이너나 팩토리와 같은 제3의 존재가 결정한다.
- 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.
UserDao의 의존관계 주입
Factory를 만들기 전 UserDao는 자신이 사용할 오브젝트의 클래스가 무엇인지 알아야만 했다.
public UserDao() {
connectionMaker = new DConnectionMaker();
}
따라서 UserDao는 설계 시점에서 DConnectionMaker라는 구체적인 클래스의 존재를 알고 있으며,
UserDao는 ConnectionMaker 뿐만 아니라 DConnectionMaker에도 의존하고 있다.
따라서 이를 해결하기 위해 제 3의 존재인 DaoFactory를 만들어 런타임 의존관계 결정 권한을 위임하였다.
DaoFactory
- UserDao와 ConnectionMaker 오브젝트 사이의 의존관계를 설정해주는 의존관계 주입을 주도
- IoC 방식으로 오브젝트의 생성과 초기화, 제공 등의 작업을 수행하는 컨테이너
- DI 컨테이너 라고도 한다.
- 방식 : 생성자를 통해 DConnectionMaker의 오브젝트를 전달해주고, 전달받은 오브젝트는 인스턴스 변수에 저장해둔다.
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
}
1.7.3 의존관계 검색과 주입
의존관계 검색
- 의존관계를 맺는 방법이 외부로부터의 주입이 아니라 스스로 검색을 이용
- 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾음(어떤 클래스의 오브젝트를 이용할지를 결정하는 것은 아님)
- 어떤 오브젝트를 이용할지 결정하는 것과 오브젝트의 생성은 외부 컨테이너에 맡기고, 이를 가져올 때 스스로 컨테이너에게 요청
public UserDao() {
DaoFactory daoFactory = new DaoFactory();
this.connectionMaker = daoFactory.connectionMaker();
}
코드를 위와 같이 수정하더라도 UserDao는 여전히 어떤 오브젝트를 사용할 지 알 수 없으며, 의존 대상 역시 ConnectionMaker 인터페이스 뿐이다.
getBean()
의존관계에 검색에 사용되는 대표적 메소드
public UserDao() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DaoFacory.class);
this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
}
의존관계 주입이 코드가 훨씬 깔끔한데 굳이 의존관계 검색을 사용하는 이유?
- 대개는 의존관계 주입을 사용하는 것이 낫다.
- 그러나 어플리케이션의 기동 시점에서 한 번은 의존관계 검색을 통해 오브젝트를 가져와야한다.
- main() 메소드에서 오브젝트를 주입받을 방법이 없다.
- 서버 역시 마찬가지
- 사용자의 요청을 받을 때마다 한 번은 의존관계 검색을 하여야한다.
의존관계 주입 vs 의존관계 검색
- 의존관계 주입 : 의존을 하는 오브젝트와 의존을 받는 오브젝트가 모두 스프링 빈이어야 한다.
- 의존관계 검색 : 검색하는 오브젝트는 스프링 빈일 필요가 없다. 단, 의존을 받는 오브젝트는 스프링의 빈이어야 한다.
1.7.4 의존관계 주입의 응용
기능 구현의 교환
앞서 ConnectionMaker를 분리한 이유는 N사와 D사 등 확장에 유연하게 대처하기 위해서였지만, 좀 더 현실적인 예를 보자면 개발용과 운영용 DB를 사용하는 경우를 생각해볼 수 있다.
- 개발 진행 시 실제 운영 DB에 접근하는 것은 무리
- LocalDBConnection과 ProductionDBConnection을 만들어 사용한다고 가정
- DI를 사용하지 않으면 개발 진행 시 모든 DBConnection을 LocalDBConnection으로 변경하고, 개발이 끝나면 다시 ProductionDBConnection으로 바꿔주어야 한다.
- DI를 사용할 경우, 한 줄만 수정하면 해결이 된다.
@Bean
public ConnectionMaker connectionMaker() {
// 개발용
return new LocalDBConnectionMaker();
// 운영용
// return new ProductionDBConnectionMaker();
}
추가적으로 QA팀이 별도의 테스트 DB만들어 이용한다고 해도, DAO에는 전혀 손을 댈 필요가 없다.
부가기능추가
DB 연결횟수를 카운팅하고 싶을 때?
- DI를 사용하지 않는다면 모든 DAO의 makeConnection() 메소드 호출부분에 카운터를 증가시키는 코드를 넣어야한다. 그리고 분석작업이 끝나면 다시 지워야한다....
- 더욱이 카운터는 DAO의 관심사가 아니다 !
- DI를 사용한다면 DAO와 DB 커넥션을 만드는 오브젝트 사이에 연결횟수를 카운팅하는 오브젝트를 하나 더 추가하면 된다.
CountingConnectionMaker.class
public class CountingConnectionMaker implements ConnectionMaker {
int counter = 0;
private ConnectionMaker realConnectionMaker;
public CountingConnectionMaker(ConnectionMaker realConnectionMaker) {
this.realConnectionMaker = realConnectionMaker;
}
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
this.counter ++;
return realConnectionMaker.makeConnection();
}
public int getCounter() {
return this.counter;
}
}
ConnectionMaker 인터페이스를 구현하여 만들었지만, 내부에서 직접 DB 커넥션을 만들지는 않는다. 대신 makeConnecion()에서 카운터를 증가시킨다.
새로운 의존관계를 컨테이너가 사용할 설정정보 : CountingDaoFactory.class
public class DaoFactory {
@Bean
public UserDao userDao() {
// UserDao는 여전히 connectionMaker()에서 생성되는 Object를 DI받는
return new UserDao(connectionMaker());
}
@Bean
public ConnectionMaker connectionMaker() {
return new CountingConnectionMaker(realConnectionMaker());
}
@Bean
public ConnectionMaker realConnectionMaker() {
return new DConnectionMaker();
}
}
실행코드 : UserDaoConnectionCountingTest.class
public class UserDaoConnectionCountingTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CountingDaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);
// Dao 사용코드
// DL을 사용하면 이름을 이용해 어떤 빈이든 가져올 수 있다.
CountingConnectionMaker ccm = context.getBean("connectionMaker", CountingConnectionMaker.class);
System.out.println("Connection counter" + ccm.getCounter());
}
}
현재는 DAO가 하나 뿐이지만 수십, 수백개여도 문제 없다.
1.7.5 메소드를 이용한 의존관계 주입
의존 오브젝트와의 관계를 주입하는 방법
- 생성자
- 현재의 UserDao
- 수정자 메소드(Setter)
- 외부로부터 제공받은 오브젝트 레퍼런스를 저장해두었다가 내부의 메소드에서 사용
- 일반 메소드
- 한 번에 여러 개의 파라미터를 받을 수 있다.
전통적으로 수정자 메소드를 이용하는 방법이 가장 보편적이다.
수정자 메소드 DI를 적용한 UserDao.class
public class UserDao {
private ConnectionMaker connectionMaker;
public void setConnectionMaker(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
}
수정자 메소드 DI를 적용한 DaoFactory.class
public class DaoFactory {
@Bean // 오브젝트 생성을 담담하는 IoC용 메소드라는 표시
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.setConnectionMaker(connectionMaker());
return userDao;
}
// ...
}
' Spring > 토비의 스프링 3.1' 카테고리의 다른 글
1.9 정리 (0) | 2019.01.11 |
---|---|
1.8 XML을 이용한 설정 (0) | 2019.01.11 |
1.6 싱글톤 레지스트리와 오브젝트 스코프 (0) | 2019.01.09 |
1.5 스프링의 IoC (0) | 2019.01.07 |
1.4 제어의 역전(IoC) (0) | 2019.01.07 |