본문 바로가기

프로그래밍/Spring

전자정부 프레임워크를 Java Config 방식으로 설정해보자(기초)

저번 글에서는 기존 전자정부 프레임워크 샘플 프로젝트의 환경 설정파일과 이에 매치되는 Java Config 설정 파일에 대해 얘기를 나누었다. 이번에는 왜 Java Config 방식으로 Spring 환경 설정을 하는지, 그리고 Java Config 파일을 만들기위해 알아두어야 할 지식에 대해 설명하고자 한다.

 

개인적으로 Java Config 방식의 가장 큰 이점은 환경설정 자체가 자바 코드이다 보니 환경 설정 과정에서 문제가 발생할 경우 이에 대한 디버깅이 쉽다는 생각을 갖고 있다. XML 환경 설정이면 Spring이 올라가는 과정에서 문제가 발생할 경우 무엇때문에 문제가 발생한건지 추적하기가 쉽지가 않다. XML에 중단점을 걸고 디버깅을 할 수가 없으니까..그러나 Java Code로 하면 Java Code이기 때문에 중단점을 걸을 수 있고 이 중단점부터 관련된 클래스의 내부 멤벼변수 값을 확인해가고 추적하면서 문제점을 알 수가 있기 때문이다. XML 방식이나 Java Config 방식이나 각자의 장/단점이 있으나 이 부분에서 나는 상당한 강점이 있다고 생각한다.

 

일단 내가 생각하는 Java Config 방식의 환경 설정 장점에 대해 얘기했고 Java Config 방식으로 Spring 환경 설정에 있어 알아두어야 할 내용을 설명하도록 하겠다.

 

1. 환경설정 역할을 하는 클래스에는 @Configuration 어노테이션을 클래스 정의시 붙여야 한다

 

Spring은 @Configuration(org.springframework.context.annotation)이 붙은 클래스를 보면 비즈니스 Bean 클래스가 아닌 우리가 일반적인 Bean 설정 XML 파일같은 환경설정 역할의 클래스라고 인식한다. 엄밀하게 얘기하면 Spring에서 제공하는 AnnotationConfigApplicationContext 클래스나 AnnoationConfigWebApplicationContext 클래스에 @Configuration이 붙은 클래스를 등록해주면 등록된 클래스의 설정된 내용을 가지고 이를 이용하여 Spring 환경 설정을 하게 된다. 사용의 형태는 다음과 같다.

 

package egovframework.example.config.root;

import org.springframework.context.annotation.Configuration;

@Configuration
public class ContextAspect {

	// Bean 클래스들을 정의한다
}

 

이전 글에서 우리가 만들어야 할 각 XML과 매핑되는 클래스 이름들을 언급한 적이 있는데 이 클래스들을 만들때 이렇게 @Configuration을 붙여주어야 한다.

 

2. @Bean 어노테이션을 붙여서 등록하고자 하는 Spring Bean을 정의한다

 

1번에서 환경설정 역할을 한다는 의미로 @Configuration 어노테이션을 붙였으면 이제 본격적으로 이 환경에 등록될 Bean 클래스들을 정의해야 한다. XML 설정 방식 당시엔 <bean> 태그를 이용해서 다음과 같이 정의했다.

 

<bean id="egovHandler" class="egovframework.example.cmmn.EgovSampleExcepHndlr" />

 

그러나 이를 Java Config 방식으로 바꿀땐 다음과 같이 한다

 

@Bean
public EgovSampleExcepHndlr egovHandler(){
	EgovSampleExcepHndlr egovSampleExcepHndlr = new EgovSampleExcepHndlr();
	return egovSampleExcepHndlr;
}

 

눈썰미가 있는 사람이라면 단박에 이 코드에 대한 규칙을 파악할 수 있을 것이다. 만드는 규칙을 정리하면 다음과 같다

 

  • 메소드 정의시 @Bean 어노테이션을 붙인다
  • 메소드의 return 타입은 만들고자 하는 Bean 클래스 타입이다
  • 메소드의 내부 코드에서는 만들고자 하는 Bean 클래스 객체를 생성한뒤 이를 return 해야 한다
  • 메소드의 이름이 Bean의 이름(XML 설정 당시 id 속성에 들어갔던 값)이다

 

첫번째는 시작할때 언급했으니 별도 설명은 필요없을듯 하고, 두번째와 세번째의 경우는 당연한거다. 환경설정 역할을 하는 것이기 때문에 해당 Bean 클래스 객체를 생성하고 이를 return 해야 Bean을 생성하게 되는 것이니까. 네번째의 경우는 까먹지 말아야 한다. 단순한 내용인데도 이를 망각해서 환경설정이 안되는 상황이 올 수도 있다. Spring의 경우 몇몇 Bean 클래스는 Default로 사용되는 이름이 있다(대표적인것이 @Transactional 어노테이션을 사용하기 위해 XML로 걸정하게 되는 <tx:annotation-driven />이다. 이 태그에는 속성으로 현재 Spring 환경 설정에서 사용하게 되는 TransactionManager Bean의 이름을 받는 transaction-manager 라는 속성이 있다. 이 태그의 transaction-manager 속성에 값을 입력하지 않으면 Spring은 transaction-manager 속성의 값으로 transactionManager를 기본으로 사용하게 된다. 즉 TransactionManager Bean의 이름을 transactionManager로 설정해야 한다는 얘기다). 이런 Default로 사용되는 이름이 있기 때문에 이런 Default 값으로 설정하려고 할 경우엔 해당 Bean 클래스를 설정하는 메소드의 이름도 그 Default 이름으로 지어주어야 한다(방금 들은 예로 다시 설명하면 TransactionManager Bean을 설정하는 메소드의 이름을 transactionManager로 지어주어야 한다는 뜻임)

 

그러면 @Bean 어노테이션이 붙은 메소드로 Spring Bean을 등록하는 것은 이제 알았으니 좀더 심화학습을 해보도록 하자. 위의 egovHandler Bean의 경우엔 별도의 설정이 없었다. 즉 <bean> 태그 설정시 id와 class만 주는 것으로 끝났다. 그러나 실제로는 그렇지 않은 Bean 설정들이 더 많다. 예를 들면 다음과 같은 형태다.

 

<bean id="defaultExceptionHandleManager" class="egovframework.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager">
	<property name="reqExpMatcher">
		<ref bean="antPathMater"/>
	</property>
	<property name="patterns">
		<list>
			<value>**service.impl.*</value>
		</list>
	</property>
	<property name="handlers">
		<list>
			<ref bean="egovHandler" />
		</list>
	</property>
</bean>

 

일반적으로 <bean> 태그의 sub 태그로 <property> 태그를 사용하며 <property> 태그에 value, ref 속성을 사용하거나 또는 하위 태그로 다시 <bean>,<ref> 등의 태그를 사용하는 형식이다. 이제 이런 태그를 어떻게 Java Code로 변환해야 하는지 보도록 하자

 

● <property> 태그는 <bean> 태그에서 사용된 클래스의 멤버변수라고 생각하자

 

윗줄에서 언급했지만 다시 얘기하자면 Bean 클래스에 정의된 멤버변수라고 생각하고서 이를 접근하도록 한다. 예를 들어 위에서 언급한 DefaultExceptionHandleManager 클래스에 다음의 <property> 태그가 있다

 

<property name="patterns">
	<list>
		<value>**service.impl.*</value>
	</list>
</property>

 

<property> 태그에 name 속성으로 patterns 값을 준 경우이다. 그러면 위에서 설명한 내용으로 patterns 란 이름의 멤버변수가 있는지 알아보자. DefaultExceptionHandleManager 클래스의 소스를 보면 patterns란 이름의 멤버변수는 없다. 그러나 클래스의 정의를 보면 다음과 같이 되어 있다.

 

public class DefaultExceptionHandleManager extends AbstractExceptionHandleManager implements ExceptionHandlerService {
...
}

 

DefaultExceptionHandleManager 클래스의 정의를 보면 AbstractExceptionHandleManager 클래스를 상속받고 있다. 이제 부모클래스인 AbstractExceptionHandleManager 클래스를 보면 다음의 멤버변수가 있다.

 

public abstract class AbstractExceptionHandleManager {

	@Resource(name = "messageSource")
	protected MessageSource messageSource;

	protected Exception ex;
	protected String thisPackageName;
	protected String[] patterns;
	protected ExceptionHandler[] handlers;
	protected PathMatcher pm;

...	
}

 

소스를 보면 String 배열로 patterns 멤버변수를 발견할 수 있다. 그러면 이 patterns 멤버변수에 값을 넣기 위한 setter 메소드가 존재할 것이다. 그리고 AbstractExceptionHandleManager 클래스 소스를 보면 patterns 멤버변수에 대한 setter 메소드를 발견할 수 있다.

 

public void setPatterns(String[] patterns) {
	this.patterns = patterns;
}

 

setter 메소드를 보면 알겠지만 당연 String 배열을 파라미터로 넣고 있다. 위에서 <property> 태그로 값을 넣은 것을 Java Code로 바꾸면 이런 형식으로 진행하면 된다

 

@Bean
public DefaultExceptionHandleManager defaultExceptionHandleManager(){
	DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager();
	defaultExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
	...
	return defaultExceptionHandleManager;
}

 

이렇게 객체를 생성한뒤 <property> 태그의 name 속성을 Bean 클래스의 멤버변수라고 생각하고 이를 설정하는 setter 메소드를 사용하여 값을 설정한다.

 

● <ref> 태그는 Bean 클래스 객체를 설정하는 것으로 생각하자

 

<property> 태그에 값 형태의 Value가 아닌 클래스 객체를 설정할 때는 ref 속성이나 <ref> 태그를 사용하게 된다. 예를 들면 위에서 언급했던 defaultExceptionHandlerManager Bean을 정의한 XML 에서 다음과 같은 부분이다.

 

<property name="handlers">
	<list>
		<ref bean="egovHandler" />
	</list>
</property>

 

<property> 태그에 name으로 handlers 로 준 경우이다. 위의 경우에 비추어 보면 handlers 라는 멤버변수가 있다는 것이다. 그리고 위에서 언급한 AbstractExceptionHandleManager 클래스 소스를 보면 ExceptionHandler 클래스 배열 타입의 멤버변수인 handlers 멤버변수가 존재하는 것을 볼 수 있다. 그리고 <ref> 태그를 이용하여 이름이 egovHandler인 Bean을 넣도록 하고 있다. 이제 이름이 egovHandler인 Bean을 멤버변수 handlers의 setter 메소드인 setHandlers 메소드를 이용해서 Java Code로 바꾸면 된다.

 

@Bean
public EgovSampleExcepHndlr egovHandler(){
	EgovSampleExcepHndlr egovSampleExcepHndlr = new EgovSampleExcepHndlr();
	return egovSampleExcepHndlr;
}

@Bean
public DefaultExceptionHandleManager defaultExceptionHandleManager(){
	DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager();
	defaultExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
	defaultExceptionHandleManager.setHandlers(new ExceptionHandler[]{egovHandler()});
	...
	return defaultExceptionHandleManager;
}

 

위에서 한 설명중에 메소드 이름이 bean 이름이 된다고 말한것이 있다. 즉 handlers 멤버변수의 setter 메소드인 setHandlers에 메소드 이름이 egovHandler인 메소드를 호출해서 그 메소드가 ExceptionHandler 인터페이스를 구현한 객체를 return 하도록 한다. 이런 식으로 해당 Bean 객체가 특정 Bean 객체를 셋팅하도록 한다.

 

3. 특정 Bean을 Injection 받아서 Bean을 생성해야 할땐 특정 Bean을 생성하는 메소드를 직접 호출, 메소드의 파라미터, 또는 클래스레벨의 @Autowired로 특정 Bean을 Injection 받을 수 있다

 

예를 들어 다음과 같은 클래스들을 Bean으로 만들어야 한다고 가정해보자

 

public class A {
	...
}

public class B {
	private A a;
	
	public void setA(A a){
		this.a = a;
	}
	
	...
}

 

이렇게 A, B 클래스를 @Bean 어노테이션을 이용해서 Bean 설정을 해야 한다고 가정해보자. A 클래스를 Bean으로 만드는데는 별 문제는 없다. 근데 가만 보면 B 클래스는 A 클래스 객체를 멤버변수로 사용하고 있기 때문에 Bean 설정된 A 클래스 객체를 받아야 한다. 즉 Spring Bean으로 관리되는 객체를 Injection 받아서 생성해야 하는 Spring Bean 클래스 설정이 되야 한다는 것이다. 만약 A와 B 모두 같은 클래스에서 설정된다면 다음과 같이 진행하면 된다.

 

@Bean
public A beanA(){
	A a = new A();
	return a;
}

@Bean
public B beanB(){
	B b = new B();
	b.setA(beanA());
	return b;
}

 

이렇게 A, B 클래스에 대한 Bean 설정을 같은 클래스에서 진행하게 될 경우 Injection 받아야 할 Bean을 생성하는 메소드를 호출하는 식으로 진행하면 된다. 여기서는 b.setA(beanA()); 가 이런 방법이 되겠다. 그러나 Spring을 모르는 사람이 자바 코드로 봤을때는 좀 오해의 소지가 되는 코드로 보일수도 있다. 예를 들어 @Bean 어노테이션이 없는 상태에서 이 코드를 바라보면 new 로 생성된 객체를 셋팅하게 되는데 Spring은 싱글톤으로 Bean 객체들이 생성되다 보니 앞뒤가 안맞는 모양으로 보일수도 있다. 그래서 그나마 좀 오해의 소지를 줄일수 있는 코드로 다음과 같이 하면 된다.

 

@Bean
public A beanA(){
	A a = new A();
	return a;
}

@Bean
public B beanB(A beanA){
	B b = new B();
	b.setA(beanA);
	return b;
}

 

이전의 코드가 Injection 받을 Bean을 생성하는 메소드를 호출했다면 지금은 Injection 받을 Bean을 메소드의 파라미터로 넣어서 Injection을 하는 식으로 구현했다. 이렇게 Bean을 생성하는 메소드의 파라미터로 Injection 받을 Bean을 설정하는 것으로 해서 Injection 받을 Bean을 받을수 있다. 만약 이 Bean이 @Configuration을 선언한 한 클래스 안에서 여러개의 Bean에 Injection 되야 한다면 @Configuration을 선언한 클래스의 멤버변수로 사용함으로써 Injection 받을 수 있다.

 

@Configuration
public class ContextAspect {

	@Autowired
	A beanA;
	
	@Bean
	public B beanB(){
		B b = new B()
		b.setA(beanA);
		return b;
	}
}

 

@Configuration 어노테이션을 붙은 클래스도 Spring에서는 Bean으로 인식이 되기 때문에(@Configuation 어노테이션은 내부적으로 @Component 어노테이션을 가지고 있다) Spring Bean에서 사용할 수 있는 @Autowired 사용이 가능한 것이다. 

 

이렇게 앞으로 설명할 내용들을 이해하기 위해 사전에 알고 있어야 할 내용들을 한번 설명해보았다. 다음글은 Sample 프로젝트의 Root Context를 구성하는 환경 파일들을 Java Config 방식으로 하나하나 바꿔가는 작업을 진행하며 설명하도록 하겠다.

 

 

목     차

 1. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(환경구축)

 2. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(기초)

 3. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-aspect.xml 변환)

 4. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-common.xml 변환)

 5. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-datasource.xml 변환)

 6. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-sqlMap.xml, context-mapper.xml 변환)

 7. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-transaction.xml 변환)

 8. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(context-idgen.xml, context-properties.xml, context-validator.xml 변환)

 9. 작성된 Java Config 클래스 파일들을 실제 등록해보자(RootContext 클래스 제작)

 10. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 1

 11. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 2

 12. 전자정부 프레임워크를 Java Config 방식으로 설정해보자(dispatcher-servlet.xml 변환) - 3