3.4.1 JDBCContext의 분리
- 전략 패턴의 구조로 봤을 때
- 클라이언트 : UserDao 메소드
- 개별적 전략 : 익명 내부 클래스로 만들어 진 것
- 컨텍스트 : jdbcContextWithStatementStrategy()
- 컨텍스트 메소드(add(), deletelAll())는 UserDao내 PrepareStratement를 실행하는 메소드(Test 클래스에서 만들어진 것들)에서 공유할 수 있음
- JDBC의 일반적 흐름인 jdbcContextWithStatementStrategy()는 다른 DAO에서 사용 가능하므로 UserDao 클래스 밖으로 독립
- 해당 클래스 이름을 JdbcContex로 함. 메소드 이름은 workWithStatementStrategy()
- 클래스 분리로 인해 Datasource는 UserDao가 아닌 JdbcContext에서 필요함
- 따라서 JdbcContext는 DataSource를 의존함
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
<bean id="userDao" class="com.ksb.spring.UserDao">
<!--다른 메소드에서 아직 datasource를 의존하기 때문에 삭제하면 안됨-->
<property name="dataSource" ref="dataSource"/>
<property name="jdbcContext" ref="jdbcContext"/>
</bean>
<bean id="jdbcContext" class="com.ksb.spring.JdbcContext">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
...
</bean>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
public class UserDaoTestJdbc {
...
@Autowired
UserDao dao;
@Test
public void addAndGet() throws SQLException {
dao.deleteAll();
...
}
}
public class UserDao {
private JdbcContext jdbcContext;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setJdbcContext(JdbcContext jdbcContext){
this.jdbcContext = jdbcContext;
}
public void add(final User user) throws SQLException {
this.jdbcContext.workWithStatementStrategy(new StatementStrategy() {
...
}
}
...
}
public class JdbcContext {
private DataSource dataSource;
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
}
public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
PreparedStatement ps = null;
Connection c = null;
try{
...
}
}
}
💡 intellij에서 메소드를 추출하여 다른 클래스로 옮길 때 리팩토링 기술 중 delegate를 이용하면 편함
3.4.2 JdbcContext의 특별한 DI - 1
- 현재 UserDao는 JdbcContext라는 구체 클래스를 의존하고 있음
- JdbcContext는 DataSource라는 인터페이스를 의존하고 있음
- UserDao는 구체 클래스를 의존하므로, 런타임이 아닌 클래스 레벨에서 의존관계가 결정됨
- 런타임 시에 DI 방식으로 외부에서 DI 하지만, 의존 오브젝트 구현 클래스(JdbcContext)를 변경할 순 없음
- DI 개념에 충실하면, 인터페이스를 사용하여 런타임시에 의존관계가 결정돼야 함
- 때문에 위의 방식은 온전한 DI라고 할 순 없을 수 있음
- 그러나, 스프링의 DI는 넓게 보면 제어권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포괄함
- 스프링을 이용해 JdbcContext를 UserDao 객체에서 사용하게 주입했다는건 DI를 기본적으로 따른다고 봐도 무방함
- 즉, 이런 방식도 DI다.
3.4.3 JdbcContext의 특별한 DI - 2
- 인터페이스를 사용하지 않았지만, JdbcContext를 UserDao와 DI 구조를 만들어야 하는 이유는 두 가지 존재
- 첫째, JdbcContext가 싱글톤 레지스트리에 관리되는 싱글톤 빈이기 때문
- JdbcContext는 자체로 변경되는 상태정보를 갖지 않음
- dataSource 인스턴스 변수는 존재하지만, 읽기 전용이기 때문에 싱글톤이 되는데 문제 없음
- JdbcContext는 메소드를 제공하는 서비스 오브젝트로서 의미가 있고, 싱글톤으로 등록되서 여러 오브젝트에 공유해 사용하는것이 이상적
- 둘째,
- JdbcContext를 통해 다른 빈에 의존하기 때문
- 이 이유가 가장 중요
- JdbcContext는 dataSoutrce 프로퍼티를 통해 DataSource 오브젝트를 주입 받도록 되어있음
- DI를 위해서는 주입되는 오브젝트와 주입받은 오브젝트 모두 스프링 빈에 등록되어야 함
- 스프링이 생성하고 관리하는 IoC 대상아이야 DI에 참여할 수 있기 때문
- 실제 스프링에 드물지만, 이러한 방식으로 인터페이스를 사용하지 않는 클래스를 직접 의존하는 DI가 등장하기도 함
- UserDao는 JdbcContext에 강한 응집도를 갖고 있고, UserDao 항상 JdbcContext 같이 쓰임
- UserDao는 JDBC 방식이 아닌 JPA나 ORM 방식을 사용하는 하이버네이트 방식을 이용하려면 JdbcContext 자체를 변경해야 함
- UserDao와 JdbcContext와 같이 강한 결합을 가지는 경우 싱글톤으로 만드는 것과 DI 필요성을 위한 스프링 빈 등록을 해도 된다.
- 하지만, 코드 구성을 DI에 적용하는 것은 가장 마지막 단계에 고려야해야 하는 문제임
- JdbcContext에 인터페이스를 두고 전형적인 DI 방식으로 UserDao에서 인터페이스를 사용하는 방법으로 해도 무방함
3.4.4 코드를 이용하는 수동 DI - 1
- JdbcContext를 스프링 빈에 등록해서 UserDao에 DI 하는 대신 직접 DI를 적용할 수 있음
- 이 방법의 경우, 싱글톤으로 만드는 것을 포기해야 함
- DAO 메소드가 호출될 때마다 JdbcContext를 생성하지 않고, DAO 마다 하나의 JdbcContext 오브젝트를 갖고 있게 함
- DAO 개수 만큼만 만들어지기 때문에 메모리 및 GC에 부담이 적음
- UserDao가 JdbcContext를 의존하기 때문에, UserDao가 JdbcContext의 생성, 초기화를 담당
- 또한, JdbcContext와 DataSource의 의존관계를 UserDao가 대신 지녀야 함
- UserDao는 dataSource가 필요 없지만, DI를 DataSource로 부터 전달받아 JdbcContext에 제공
- 제공할 때 JdbcContext의 수정자 메소드(Setter)로 주입
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
<bean id="userDao" class="com.ksb.spring.UserDao">
<!--<property name="connectionMaker" ref="connectionMaker"></property>-->
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
...
</bean>
</beans>
public class UserDao {
private JdbcContext jdbcContext;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.jdbcContext = new JdbcContext();
this.jdbcContext.setDataSource(dataSource);
//아직 JdbcContext를 적용하지 않은 메소드를 위해 저장해둠
this.dataSource = dataSource;
}
}
3.4.5 코드를 이용하는 수동 DI - 2
- setDataSource() 메소드는 DataSource 오브젝트를 주입해줄 때 호출 됨
- 이때 수동 DI 작업을 하면 됨
- 이 방법은 굳이 인터페이스를 두지 않아도 긴말한 관계를 갖는 클래스를 빈으로 분리하지 않고 다른 오브젝트에 DI를 적용할 수 있다는 장점이 있음
- 수정자 메소드를 통해 다른 오브젝트를 초기화하고 코드를 이용해 DI 하는 것은 스프링에서 종종 사용됨
- 밀접한 관계를 갖는 클래스에 DI 적용하는 방법 두 가지를 확인해 봤음
- 스프링을 이용한 DI
- 수동 DI
- 스프링을 이용한 DI는 실제 의존관계가 설정파일에 명확히 보임
- 하지만, DI의 근본적인 원칙에 부합하지 않는 구체적인 클래스와의 관계가 설정에 직접 노출되는 단점이 있음
- 수동 DI는 내부에서 관계가 만들어지기 때문에 외부로 관계가 드러나지 않음
- 하지만, 싱글톤으로 만들 수 없고 DI 작업을 위한 부가적인 코드 필요
'토비의 스프링 정리' 카테고리의 다른 글
토비의 스프링 - 3.6 스프링의 JdbcTemplate (0) | 2022.09.30 |
---|---|
토비의 스프링 - 3.5 템플릿과 콜백 (0) | 2022.09.30 |
토비의 스프링 - 3.3 JDBC 전략 패턴의 최적화 (0) | 2022.09.29 |
토비의 스프링 - 3.2 변하는 것과 변하지 않는 것 (0) | 2022.09.29 |
토비의 스프링 - 3.1 다시 보는 초난감 DAO (0) | 2022.09.29 |