본문 바로가기

프로그래밍/Spring

@Configuration과 @Bean을 이용한 Spring Framework 환경 설정의 Java 코드화..

흔히들 Spring Framework(이하 Spring)를 사용하면서 가장 많이 사용하는 환경설정 방법이 아마 XML일것으로 생각한다. 그나마 2.5때는 Spring에서 사용되는 모든 Bean을 전부 XML에 등록하는 식으로 개발 가이드가 되었기 때문에 Spring에서 사용하는 모든 Bean과 Bean들간의 연결 설정을 XML에 기록해야만 했다(참고로 필자가 Spring Framework를 실제 프로젝트에서 적용한건 Spring Framework 3.0.X버전대 부터이지만 필자의 지인의 경우는 Spring Framework 2.5.X 대 버전으로 프로젝트를 진행했었던지라 그 경험에 비추어 적어놓는다). 그러나 Spring 3.0.X대 부터는 어노테이션을 이용한 Bean의 등록 및 Bean들 간의 연결 설정이 편해진 덕분에 XML에 기록해야 될 양이 상당히 많이 줄었다. XML에 기록해야 하는 경우는 AOP나 트랜잭션 등 특수한 기법을 사용한 내용에 대한 Bean에 대해서만 적고 기타 일반 비즈니스 로직이 있는 Bean은 @Component, @Controller, @Service, @Repository, @Resource, @Autowired 등의 어노테이션들을 관련 Bean의 Java 코드에 이용하여 Bean을 등록하고 Bean간의 연결 관계를 설정하는 식으로 XML에 대한 의존도를 상당히 많이 줄였다.

 

그러나 이렇게 많이 줄인 환경 설정이지만 때때로 이런 설정들 조차 Java 코드에 넣어서 활용하고 싶은 경우가 종종 발생하게 된다. 대표적인 예 중의 하나가 DataSource 설정이라 생각한다. 우리가 흔히 XML에 DataSource를 설정할때 넣는 정보들을 생각해보자. Database 서버의 IP가 들어가고 별도 부가정보가 들어가며 Database 계정 이름과 비밀번호를 넣게 된다. 근데 운영하는 쪽 입장에서는 이것이 아무런 보호장치 없이 텍스트 파일 기반의 XML에 설정하여 그대로 노출된다는 것이 불편할수도 있다. 실제로 이 문제는 많은 개발자들이 현업에서 겪었을 것이라 생각한다(필자는 직접 겪은적은 없으나 위에 언급했던 지인은 그런 상황을 겪은 적이 있다) 이 문제는 사실 단순하게 해결할수 있는 방법도 있다. 즉 XML에서 DataSource를 설정할때 등록하는 클래스(예를 들어 Apache의 Common DBCP를 사용할 경우 org.apache.commons.dbcp.BasicDataSource 클래스가 될 것이다)를 상속받은 클래스를 하나 만든뒤에 그 클래스에 set 메소드를 만들어 set 메소드 내부에 관련 로직을 넣어서 문제를 해결할 수 있다. 즉 set 메소드의 파라미터로 사용자가 암호화된 값을 넣으면 set 메소드 내부에서는 암호화 된 값을 Decrypt  작업을 거쳐 암호화하기 전의 원래 데이터를 만든뒤에 이것을 클래스의 멤버변수에 셋팅하는 식이다. 그러나 이럴 경우 대표적인 문제가 있다.별도로 부가 클래스를 만들어야 한다는 점이다. 이게 머 문제냐..라고 반문 할 수도 있다. 사실 이러한 상황이 1~2건일 경우에는 큰 문제라 할것은 없다. 그러나 이런 상황이 여러가지가 발생하게 되면 그때그때마다 기존 클래스를 상속받은 새로운 클래스를 만들게 되고 이렇게 되면 관리해야 할 클래스가 늘어나게 된다. 이거는 바람직하지 않다고 생각한다. 만약 기존 클래스를 상속받은 새로운 클래스를 만들지 않고 기존 클래스를 활용하면서 이러한 설정 정보를 Java 코드에 넣어서 구현한다면 설사 이런 상황이 많이 발생하더라도 늘어나는 클래스는 없기 때문에 관리하기가 용이하다. 그래서 이 글에서는 @Configuration과 @Bean을 이용한 DataSource 설정을 함으로써 이러한 주제를 풀어보고자 한다.

 

XML을 이용한 DataSource 설정을 보면 흔히 다음과 같은 형태로 한다.(Oracle을 예로 들었다)

 

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
	<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
	<property name="username" value="study" />
	<property name="password" value="study" />
	<property name="defaultAutoCommit" value="false" />
</bean>

 

사용하는 Database나 Connection Pool 클래스에 따라 구체적인 내용은 다를 수 있으나 기본적인 구조는 이런 식으로 설정할 것이다. 이제 이 내용을 자바 코드로 바꾸어보자. Spring에서 특정 Java 클래스가 Spring의 환경 설정이라는 것으로 인식시키기 위해서는 관련 클래스에서 @Configuration 어노테이션을 사용해야 한다. 기본적인 골격 구조는 다음과 같다.

 

package com.terry.boardprj.common;

import org.springframework.context.annotation.Configuration;

@Configuration
public class BoardConfig {
    
}

 

클래스의 상단에 @Configuration 어노테이션을 사용함으로써 이 Java 클래스는 Spring의 환경 설정과 관련된 파일이다라는 식으로 알려주게 된다. 그러나 현재 우리가 할 내용을 생각해보면 지금 이 상태에서는 빈 껍데기이다. 왜냐면 DataSource를 던져주는 Bean 설정이 없기 때문이다. 이제 Bean을 설정하는 과정을 살펴보자.

 

위에서 언급했던 DataSource 설정 XML을 다시 상기해보자. 설정 내용을 보면 이 Bean의 id는 dataSource이고 이 Bean의 클래스는 org.apache.commons.dbcp.BasicDataSource 클래스이며 destroy-method 속성 값은 close이다. 그리고 이 빈의 Property, 다시 말해 BasicDataSource 클래스에서 설정해야 할 멤버변수로 driverClassName, url, username, password, defaultAutoCommit이 있고 각각의 Property에 관련 값을 설정하고 있다. 즉 우리가 이러한 내용을 지금 만들고자 하는 Spring 환경 설정 Java 코드에 녹아들어가야 한다는것이다. Spring 환경 설정 클래스에서는 Spring에서 사용하는 Bean을 제공하는 기능을 @Bean 어노테이션을 설정함으로써 이런 기능을 한다고 밝혀준다. 그래서 방금 얘기한 내용을 생각하면서 다음의 코드를 보면 이해할수 있을것이다.

 

package com.terry.boardprj.common;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BoardConfig {
    
    @Bean(destroyMethod="close")
    public DataSource dataSource(){
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:orcl");
        dataSource.setUsername("study");
        dataSource.setPassword("study");
        dataSource.setDefaultAutoCommit(false);
	
        return dataSource;
    }
}

 

위의 Java 코드를 보자. @Bean 어노테이션을 함수 위에 언급함으로써 이 함수는 Spring에서 사용하는 Bean을 리턴해준다는 것을 언급해준다. 이 @Bean 어노테이션의 속성 destroyMethod에 close를 설정함으로써 위에서 언급했던 XML로 Bean 설정하는 내용중의 destroy-method="close" 기능을 하게 된다. 함수명을 dataSource라고 함으로써 bean 태그의 id와 동일하게 맞춰주게 된다. 그래서 차후에 XML에서 ref 속성을 이용하여 위의 Bean을 참조하게끔 할때 이 함수명을 그대로 써주면 된다. 함수 내부를 보자. BasicDataSource 클래스 객체를 하나 만든뒤에 이 객체의 setter 들을 이용해서 관련 값들을 입력하고 있다. 이 setter들을 보자. 예를 들어 setDriverClassName setter를 보자. 이 setter는 자바의 getter/setter 네이밍 규약에 따라 보면 driverClassName 멤버변수 setter 라는 것을 알 수 있게 된다. 위에서 언급했던 XML의 driverClassName Property 태그와 동일하게 된다. 이런 식으로 나머지 setter를 보면 XML에서 설정하는 것과 동일하게 된다. 그리고 마지막으로 이렇게 만든 객체를 return 하게 된다. 이렇게 XML에서 우리가 Bean을 설정한 내용과 동일한 기능을 제공하게 되는 것이다.

 

만약 위에서 언급한 예와 같이 암호로 들어간 입력값을 풀어야 한다면 위의 함수 코드에 암호화 된 값을 Decrypt하는 로직을 넣어서 복호화 된 값을 setter 함수에 넣어주면 된다. 또 @Configuration 어노테이션으로 설정한 클래스는 Spring에서 사용하는 Bean 자체로도 인식이 된다. 그래서 만약 암복호화 서비스를 자체적으로 만든것이 있다면 다음과 같이 위의 Bean에 DI가 되도록 하여 작업하면 된다.

 

package com.terry.boardprj.common; 

import javax.sql.DataSource; 
import org.apache.commons.dbcp.BasicDataSource; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 

@Configuration 
public class BoardConfig { 

    @Autowired DecryptService decrypt; 
  
    @Bean(destroyMethod="close") 
    public DataSource dataSource(){ 
        BasicDataSource dataSource = new BasicDataSource(); 
        dataSource.setDriverClassName(decrypt.decrypt("XZRFGHBVDCSGJUMBGIOPKNBGCDDWWZAWQ#VBYHN")); 
        dataSource.setUrl(decrypt.decrypt("KBMCGYUIHGDEXXZSERC")); 
        dataSource.setUsername(decrypt.decrypt("NBVXWERTGGHUIKAWERC")); 
        dataSource.setPassword(decrypt.decrypt("MLPITJGNSEWSXCVDXSQAZCV")); 
        dataSource.setDefaultAutoCommit(false); 
        return dataSource; 
    } 
}

 

이 코드를 보면 한가지 의아스러운 것이 있을것이다. 함수 안에서 객체를 생성해서 리턴하는 식으로 코딩할 경우 Spring에서 각 Bean을 Singleton으로 관리하는 기본 설정에 위배가 되기 때문이다. 그러나 Spring에서는 @Configuration 어노테이션을 사용한 클래스에서 @Bean 어노테이션을 사용한 함수가 리턴하는 객체는 자체적으로 Singleton으로 관리해준다. 때문에 이 부분에 대한 걱정은 하지 않아도 된다.

 

그러나 이렇게 클래스를 만들어도 이 클래스가 바로 환경 설정에 이용될 수는 없다. 왜냐면 이 클래스가 Spring에 등록이 되지 않으면 이 클래스가 환경 설정 클래스라는 인식이 될 수 없기 때문이다. 그래서 이 클래스를 Spring에 등록하는 과정을 설명하고자 한다. 단순하게 XML에 Bean 태그를 써서 할 수도 있으나 @Configuration과 같이 어노테이션 기반으로 Bean을 등록하는 설정을 하고 있다면 내부적으로 Spring의 빈 스캐닝을 이용해서 Bean을 등록하고 있는 것이기 때문에 Spring의 빈 스캐닝 과정에 이 @Configuration 어노테이션을 사용한 클래스도 같이 스캔이 되도록 하면 된다. 다음과 같은 XML 설정을 해주면 된다

 

<context:component-scan base-package="com.terry.boardprj">
	<context:include-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

 

이렇게 org.springframework.context.annotation.Configuration이 component-scan에서 include 되도록 하면 @Configuration 어노테이션을 사용한 클래스를 Spring에 등록하게 해준다. 위의 예에서는 DataSource 설정 한가지만 다루었지만 만약 새로운 경우가 생겨서 특정 설정을 자바 코드 안에 감춰야 한다면 위에서 만든 클래스에 @Bean 어노테이션을 사용한 함수를 하나 더 만들어주면 된다. 이런식으로 확장함으로써 별도로 상속받아 새로이 클래스를 만드는 과정이 없어져서 클래스 갯수가 늘어나지 않게 된다.

 

주의사항 : @Configuration 어노테이션을 사용하기 위해서는 CG-LIB를 이용해야 한다. Spring에서 CG-LIB가 사용되는 경우가 인터페이스를 상속받은 클래스가 아닌 일반 클래스에 AOP를 적용할 경우 이용되는 것을 생각해보면 혹시 이제껏 위에서 설명한 내용이 AOP를 적용하여 구현 되는 것일수도 있겠다는 추측이 들지만 확인되지는 않았다. 만약 확인하고 싶다면 특정 인터페이스를 만든뒤 여기서 bean의 id와 동일한 함수명을 사용한뒤 환경 설정 클래스에서는 이 인터페이스를 구현하고 CG-LIB를 뺀 후에 테스트해보면 되지 않을까 싶다.

(이 글을 작성한 2013년 4월 18일 시점에서 Spring Framework 3.0.X 버전으로 @Configuration을 적용하였을땐 CG-LIB가 필요했으나 Spring Framework 3.1 버전부터는 @Configuration에 대한 본격적인 지원이 이루어지기 때문에 CG-LIB가 필요없다)

암튼 CG-LIB을 이용하기 때문에 CG-LIB 라이브러리를 추가해주어야 한다. 프로젝트가 Maven 기반 프로젝트라면 pom.xml에 다음과 같이 넣어주면 된다

 

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>