본문 바로가기

토비의 스프링 정리

토비의 스프링 - 3.4 컨텍스트와 DI

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 적용하는 방법 두 가지를 확인해 봤음
    1. 스프링을 이용한 DI
    2. 수동 DI
  • 스프링을 이용한 DI는 실제 의존관계가 설정파일에 명확히 보임
  • 하지만, DI의 근본적인 원칙에 부합하지 않는 구체적인 클래스와의 관계가 설정에 직접 노출되는 단점이 있음
  • 수동 DI는 내부에서 관계가 만들어지기 때문에 외부로 관계가 드러나지 않음
  • 하지만, 싱글톤으로 만들 수 없고 DI 작업을 위한 부가적인 코드 필요