3. 템플릿 - 컨텍스트와 DI

이유석·2022년 6월 20일
0

Book - Toby's Spring

목록 보기
17/20
post-thumbnail

컨텍스트와 DI

3.4.1 JdbcContext의 분리

지금까지 작성한 코드를 전략 패턴의 구조로 분석해보자.

  • 클라이언트 : UserDao의 메소드
  • 개별 전략 : 익명 내부 클래스로 만들어진 것
  • 컨텍스트 : jdbcContextWithStatementStrategy() 메소드

이때, JDBC의 일반적인 작업 흐름을 담고 있는 jdbcContextWithStatementStrategy() 는 다른 DAO에서도 사용 가능하다.

그러니 jdbcContextWithStatementStrategy()를 UserDao 클래스 밖으로 독립시켜 모든 DAO가 사용할 수 있도록 해보자.


클래스 분리

  • 분리해서 만들 클래스의 이름은 JdbcContext이다.
  • UserDao에 있던 컨텍스트 메소드를 workWithStatementStrategy()라는 이름으로 옮긴다.

이렇게 하면 DataSource를 필요로 하는 것은 UserDao가 아니라 JdbcContext가 돼버린다.
DB 커넥션을 필요로 하는 코드가 JdbcContext 안에 있기 때문이다.

따라서 JdbcContext가 DataSource 타입 빈을 DI 받을 수 있도록 코드를 수정해준다.

public class JdbcContext {

    private DataSource dataSource;

    // DataSource 타입 빈을 DI 받을 수 있게 준비해둔다.
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    // jdbcContextWithStatementStrategy() 메소드의 이름을 변경해준다.
    public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
        Connection c = null;
        PreparedStatement ps = null;

        try {
            c = this.dataSource.getConnection();

            ps = stmt.makePreparedStatement(c);

            ps.executeUpdate();
        } catch (SQLException e) {
            throw e;
        } finally {
        	if (ps != null) {try {ps.close();} catch (SQLException e) {}}
            if (c != null) {try {c.close();} catch (SQLException e) {}}
        }
    }
}

이제 UserDao가 분리된 JdbcContext를 DI 받아서 사용할 수 있도록 변경해주자.

public class UserDao {
	...
    // JdbcContext 를 DI 받도록 만든다.
    private JdbcContext jdbcContext;

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

    public void add(final User user) throws SQLException {
		
        // DI 받은 JdbcContext의 컨텍스트 메소드를 사용하도록 변경하였다.
        this.jdbcContext.workWithStatementStrategy(
            new StatementStrategy() {
                public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                    PreparedStatement ps = c.prepareStatement(
                            "insert into users(id, name, password) values(?,?,?)");

                    ps.setString(1, user.getId());
                    ps.setString(2, user.getName());
                    ps.setString(3, user.getPassword());

                    return ps;
                }
            }
        );
    }
    
    ...
    
    public void deleteAll() throws SQLException {
    	this.jdbcContext.workWithStatementStrategy (
        	new StatementStrategy() {...}
        );
    }
    
    ...
}

빈 의존관계 변경

새롭게 작성된 오브젝트 간의 의존관계를 살펴보고 이를 스프링 설정에 적용해보자.

  • UserDao 는 이제 JdbcContext에 의존하고 있다.
  • JdbcContext는 인터페이스인 DataSource와 달리 구체 클래스이다.

스프링의 DI는 기본적으로 인터페이스를 사이에 두고 의존 클래스를 바꿔서 사용하도록 하는게 목적이다.
하지만, JdbcContext는 JDBC 컨텍스트를 제공해주는 서비스 오브젝트로서 의미가 있을 뿐이고, 구현 방법이 바뀔 가능성은 없다.
따라서 UserDao와 JdbcContext는 인터페이스를 사이에 두지 않고 DI를 적용하는 특별한 구조가 된다.

스프링 빈 설정은 클래스 레벨이 아니라 런타임 시에 만들어지는 오브젝트 레벨의 의존관계에 따라 정의된다.

빈으로 정의되는 오브젝트 사이의 관계를 그려보면 아래와 같다.

빈 의존관계에 따라서 XML 설정 파일을 수정하자.

test-applicationContext.xml 파일을 아래와 같이 수정한다.

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

    <bean id="userDao" class="springbook.user.dao.UserDao">
        <property name="dataSource" ref="dataSource" />
        <property name="jdbcContext" ref="jdbcContext" />
    </bean>
</beans>
  • UserDao 내에 아직 JdbcContext를 적용하지 않은 메소드가 있기 때문에 UserDao의 dataSource property를 제거하지 않았다.

테스트를 위해 변경된 설정파일을 쓰도록 테스트 코드 설정 부분을 아래와 같이 변경해주자.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/test-applicationContext.xml")
@DirtiesContext
public class main {

    @Autowired
    ApplicationContext context;

    private UserDao userDao;

	...

    @Before
    public void setUp() {
        this.userDao = this.context.getBean("userDao", UserDao.class);

		...
    }
    
    ...
}

3.4.2 JdbcContext의 특별한 DI

DI(의존관계 주입)의 개념을 충실히 따르면

  • UserDao는 인터페이스를 거쳐서 런타임 시에 생성된 JdbcContext 의 구체 클래스를 사용하여야 한다.

  • 하지만, 현재는 인터페이스를 거치지 않고 JdbcContext 클래스를 직접 바로 사용하고 있다.

이때, DI로 볼 수 없다고 생각할 수 있지만, 스프링의 DI를 넓게 보면 객체의 생성과 관계 설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC 개념을 포괄한다.
즉 JdbcContext를 스프링을 이용해 UserDao 객체에서 사용하게 주입했다는 것은 DI의 기본을 따르고 있다고 볼 수 있다.

JdbcContext를 UserDao와 DI 만들어야 하는 이유는 아래와 같다.

  1. JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글통빈이 되기 때문이다.

  2. JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문이다.

  3. 또한 인터페이스가 없다는 것은 UserDao와 JdbcContext가 매우 긴밀한 관계를 가지고 강하게 결합되어 있다는 의미이다.


코드를 이용하는 수동 DI

JdbcContext를 스프링 빈으로 등록해서 UserDao에 DI하는 대신 사용할 수 있는 방법이 있다.
UserDao 내부에서 코드로 직접 DI를 적용하는 방법이다.

이 방법을 쓰려면 JdbcContext를 스프링의 빈으로 등록해서 사용했던 이유인 싱글톤으로 만들려는 것은 포기해야 한다.

그렇다고 DAO 메소드가 호출될 때마다 JdbcContext 오브젝트를 새로 만드는 것은 아니다.
DAO 마다 하나의 JdbcContext 오브젝트를 갖도록하는 것 이다.

JdbcContext를 스프링 빈으로 등록하지 않았으므로 누군가 JdbcContext의 생성과 초기화를 책임져야 한다.
JdbcContext의 제어권은 JdbcContext를 사용할 UserDao가 갖는것이 적당하다.

또 다른 문제는, JdbcContext는 스프링 빈을 통해 다른 빈을 인터페이스를 통해 간접적으로 의존받고 있다.
이때, 의존 오브젝트를 DI를 통해 제공받기 위해서 자신도 빈으로 등록돼어 있어야 하기 때문이다.

위 문제를 해결할 방법은 JdbcContext에 대한 제어권을 갖고있는 UserDao에게 DI까지 맡기는 것이다.

즉, JdbcContext가 필요로 하는 DataSource 빈을 UserDao가 대신 DI 받도록 하면 된다.

JdbcContext를 UserDao에서 코드를 통해 DI 해주는 방식으로 변경하면 빈의 의존관계는 아래와 같이 변경된다.

위 그림대로 설정 파일을 변경해주자.

<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
    <bean id="testDataSource" ...>
      ...
    </bean>

    <bean id="userDao" class="springbook.user.dao.UserDao">
        <property name="dataSource" ref="testDataSource" />
    </bean>
</beans>

이전에 설정파일에 등록했던 JdbcContext 빈을 제거하고, UserDao의 JdbcContext 프로퍼티를 제거하면 된다.

이제 코드를 통해서 JdbcContext를 DI 받도록 하자.

public class UserDao {

    private DataSource dataSource;
    private JdbcContext jdbcContext;

    public void setDataSource(DataSource dataSource) {
        // JdbcContext 생성 (IoC)
        this.jdbcContext = new JdbcContext();

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

        // 아직 JdbcContext를 적용하지 않은 메소드를 위해 남겨둔다.
        this.dataSource = dataSource;
    }
    
    ...
}

UserDao는 이제 JdbcContext를 외부에서 주입받을 필요가 없기 때문에 setJdbcContext()는 제거해준다.

이 방법의 장점은 아래와 같다.

  • 긴밀한 관계를 갖는 DAO 클래스와 JdbcContext를 어색하게 따로 빈으로 분리하지 않고 내부에서 직접 만들어 사용하면서도 다른 오브젝트에 대한 DI를 적용할 수 있다는 점이다.

소스코드 : github

profile
https://github.com/yuseogi0218

0개의 댓글