본문 바로가기

Spring/토비의 스프링 3.1

3.4 컨텍스트와 DI


3.4.1 JdbcContext의 분리

전략 패턴의 구조

  • 클라이언트 : UserDao의 메소드 (add, deleteAll)
  • 개별 전략 : 익명 내부 클래스
  • 컨텍스트 : jdbcContextWithStatementStrategy()

JDBC 일반적인 흐름을 가진 jdbcContextWithStatementStrategy()는 다른 DAO에서도 사용 가능하다.

  • 클래스 밖으로 독립시키자

클래스 분리

JdbcContext

  • 분리된 클래스
  • 컨텍스트 메소드 : workWithStatementStrategy()
  • UserDao는 더 이상 DataSource가 필요없으며, JdbcContext에 DataSource가 필요하다
public class UserDao {
    // getCount 등 메소드에 여전히 DataSource를 사용하고 있으므로 제거하지 않음
    DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private JdbcContext jdbcContext;

    public void setJdbcContext(JdbcContext jdbcContext) {
        this.jdbcContext = jdbcContext;
    }

    public void add(final User user) throws SQLException {
        this.jdbcContext.workWithStatementStrategy(
            // ...
        );
    }

    public void deleteAll() throws SQLException {
        this.jdbcContext.workWithStatementStrategy(
            // ...
        );
    }
}

빈 의존관계 변경

UserDao는 이제 JdbcContext에 의존

  • JdbcContext는 인터페이스가 아닌 구체적인 클래스임
  • 기본적으로 스프링의 DI는 인터페이스를 사이에 두지만, JdbcContext의 경우 독립적인 Jdbc 컨텍스트를 제공해주는 서비스 오브젝트로서의 의미만 있을 뿐 바뀔 가능성이 없음

새로운 의존관계

  • userDao:UserDao -> jdbcContext:JdbcContext -> dataSource:SimpleDriverDS

XML 수정

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.david.tobysspring.user.dao.UserDao">
        <property name="dataSource" ref="dataSource" />
        <property name="jdbcContext" ref="jdbcContext" />
    </bean>

    <!-- ... -->

    <bean id="jdbcContext" class="com.david.tobysspring.user.dao.JdbcContext">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

책에 없지만 UserDaoTest에서 dataSource와 jdbcContext를 주입하는 코드 추가해주어야 함

public class UserDaoTest {
    UserDao dao;
    JdbcContext jdbcContext;

    // ...

    @Before
    public void setUp() {
        dao = new UserDao();
        jdbcContext = new JdbcContext();

        DataSource dataSource = new SingleConnectionDataSource("jdbc:oracle:thin:@localhost:1521:xe", "springbook_test", "test", true);
        dao.setDataSource(dataSource);

        jdbcContext.setDataSource(dataSource);
        dao.setJdbcContext(jdbcContext);

        this.user1 = new User("gyumee", "박성철", "springno1");
        this.user2 = new User("leegw700", "이길원", "springno2");
        this.user3 = new User("bumjin", "박범진", "springno3");
    }
}

3.4.2 JdbcContext의 특별한 DI

UserDao와 JdbcContext 사이에 인터페이스를 사용하지 않고 DI를 적용함

  • UserDao와 JdbcContext가 클래스 레벨에서 의존관계가 결정됨

스프링 빈으로 DI

인터페이스를 사용하지 않고 DI를 적용해도 문제가 없을까?

  • 인터페이스를 두어도 상관없지만, 반드시 그렇게 할 필요는 없다.
  • 스프링의 DI : IoC 개념(객체의 설정과 관계설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임)을 포괄
  • 따라서 DI의 기본을 따르고 있다.

클래스를 자유롭게 변경할 수 있게 하지도 않았는데 DI 구조로 만들어야 할 이유

  1. JdbcContext가 싱글톤 레지스트리에서 관리되는 싱글톤 빈이기 때문
    • JdbcContext는 그 자체로 변경되는 상태정보를 가지고 있지 않음
    • dataSource라는 인스턴스 변수가 있지만, dataSource는 읽기전용이므로 문제가 없음
    • JdbcContext는 서비스 오브젝트로서 의미가 있고, 그래서 싱글톤으로 여러 오브젝트에서 공유해 사용되는 것이 이상적
  2. JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문
    • DI를 위해서 주입되는 오브젝트와 주입받는 오브젝트 모든 스프링 빈이어야 한다.

그렇다면 왜 인터페이스를 쓰지 않는 것인가?

  • UserDao와 JdbcContext가 긴밀한 관계를 가지고 강하게 결합되어 있음을 의미
  • 둘은 강한 응집도를 가지고 있다.
  • 클래스는 구분되어 있으나 항상 함께 사용되어야 한다.
  • UserDao가 JDBC 대신 JPA 등과 같은 ORM으로 바뀌면 JdbcContext는 통째로 바뀌어야한다.
  • 테스트에서도 JdbcContext는 다른 구현으로 대체해서 사용할 이유가 없다.

이런 예외적인 경우에 인터페이스 없이 DI를 하는 것이 가능하다. 단, 가장 마지막 단계에 고려해볼 사항이다.

코드를 이용하는 수동 DI

UserDao 내부에서 직접 DI를 적용하는 방법

  • JdbcContext를 싱글톤으로 만드는 것은 포기하여야 함
  • Dao 메소드가 호출될 때마다 JdbcContect 오브젝트를 새로 생성하는 것은 아니고, DAO마다 하나의 JdbcContext 오브젝트를 갖고 있게 하는 것

JdbcContext를 스프링 빈으로 등록하지 않았으므로 누군가 JdbcContext의 생성과 초기화를 책임져야 한다.

  • UserDao가 갖는 것이 적당함
  • 자신이 사용할 오브젝트를 직접 만들고 초기화하는 전통적인 방법 사용

JdbcContext는 다른 빈을 인터페이스를 통해 간접적으로 의존하고 있다.

  • 빈으로 등록되어 있지 않은 경우에는?
  • DataSource 타입 빈을 다이내믹하게 주입받아서 사용해야 한다.
  • UserDao에게 DI까지 맡겨서 UserDao가 임시로 DI 컨테이너처럼 동작하게 한다.

applicationContext.xml

  • userDao와 dataSource만 정의
  • userDao 빈에 DataSource 타입 프로퍼티를 지정해서 dataSource 빈을 주입받도록 함

UserDao

  • UserDao는 JdbcContext 오브젝트를 만들면서 DI 받은 DataSource 오브젝트를 JdbcContext의 수정자 메소드로 주입해준다.
  • 만들어진 JdbcContext 오브젝트는 UserDao의 인스턴스 변수에 저장해두고 사용한다.

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.david.tobysspring.user.dao.UserDao">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <!-- ... -->
    </bean>

</beans>

설정 파일을 보면 UserDao가 직접 DataSource를 의존하고 있는 것 같지만, 내부적으로는 JdbcContext를 통해 간접적으로 DataSourse를 사용하고 있을 뿐이지만 JdbcContext를 UserDao와 묶어서 userDao 빈이라고 생각해보면 빈 레벨에서 userDao 빈이 dataSource 빈에 의존

UserDao

  • JdbcContext를 외부에서 주입받을 필요가 없음
  • setJdbcContext()를 제거하고 setDataSource()를 수정
public class UserDao {
    DataSource dataSource;
    private JdbcContext jdbcContext;

    /**
     * 수정자 메소드이면서 JdbcContext에 대한 생성, DI 작업을 동시에 수
     * @param dataSource
     */
    public void setDataSource(DataSource dataSource) {
        // JdbcContext 생성(IoC)
        this.jdbcContext = new JdbcContext();

        // 의존 오브젝트 주입(DI)
        this.jdbcContext.setDataSource(dataSource);

        // for 아직 JdbcContext를 적용하지 않은 메소드
        this.dataSource = dataSource;
    }

    // ...
}

setDataSource() 메소드

  • DI 컨테이너가 DataSource 오브젝트를 주입해줄 때 호출
  • JdbcContext에 대한 수동 DI 진행
  • DAO 클래스와 JdbcContext를 어색하게 따로 빈으로 분리하지 않고, 내부에서 직접 만들어 사용
  • 다른 오브젝트에 대한 DI 적용 가능

인터페이스를 사용하지 않고 DAO와 밀접한 관계를 갖는 클래스에 DI에 적용하는 방법

  1. 스프링 DI를 이용하기 위해 빈으로 등록하는 방법
    • 장점 : 의존관계가 설정파일에 명확하게 드러남
    • 단점 : 구체적인 클래스와의 관계가 설정에 직접 노출됨
  2. DAO코드를 이용한 수동 DI
    • 장점 : Dao 내부에서 만들어지고 사용되면서 그 관계를 외부에 드러내지 않음. 내부에서 은밀히 DI를 수행하고 그 전략을 외부에 감출 수 있음
    • 단점 : 여러 오브젝트가 사용하더라도 싱글톤으로 만들 수 없고, DI를 위한 부가적인 코드가 필요함

상황에 따른 적절한 방법을 선택하여야 한다.

  • 선택을 하면 그에 대한 분명한 이유와 근거가 있어야한다.
  • 그렇지 않다면 평범한 DI 구조를 만드는 게 나을 수 있다.


' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

3.6 스프링의 JdbcTemplate  (0) 2019.01.23
3.5 템플릿과 콜백  (0) 2019.01.23
3.3 JDBC 전략 패턴의 최적화  (0) 2019.01.18
3.2 변하는 것과 변하지 않는 것  (0) 2019.01.18
3.1 다시보는 초난감 DAO  (0) 2019.01.17