본문 바로가기

프로그래밍/Spring

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

지난 글에서는 전자정부 프레임워크의 환경설정 XML 파일을 Java Config 파일로 변환하는데 알아야 할 기본적인 내용에 대해 설명했다. 이번 글부터는 구체적으로 특정 환경 파일을 Java Config 파일로 하나하나 변환시키는 과정에 대해 다루어보도록 하겠다.

 

전자정부 프레임워크의 context-aspect.xml은 프로젝트에서 AOP 관련 설정이 지정된 XML 파일이다. 전자정부 프레임워크 샘플 프로젝트에서 설정된 context-aspect.xml에는 샘플 프로젝트에서 동작중인 Service Bean들이 동작 도중 예외가 발생하면 예외를 던지면서 후처리 로직으로 ExceptionTransfer.class의 transfer 메소드가 실행이 되도록 설정되어 있다. 이제 이 부분을 하나하나 바꿔보는 작업을 진행해보자.

 

context-aspect.xml을 보면 bean을 만드는 과정에 있어서 특정 bean을 참조하지 않고 만들어지는 bean이 있다. 바로 다음의 bean들이다.

 

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

 

이렇게 특정 bean을 참조하지 않고 만들어지는 bean들을 먼저 @Bean 어노테이션을 이용해서 만든다. 

 

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

@Bean
public EgovSampleOthersExcepHndlr otherHandler(){
	EgovSampleOthersExcepHndlr egovSampleOthersExcepHndlr = new EgovSampleOthersExcepHndlr();
	return egovSampleOthersExcepHndlr;
}

 

그런뒤 이렇게 만든 bean을 참조하여 만드는 bean을 만든다. context-aspect.xml에서 보면 다음의 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 id="otherExceptionHandleManager" 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="otherHandler" />
		</list>
	</property>
</bean>

 

2개의 bean 모두 같은 타입의 클래스로 만들어지지만 handlers property에서 참조되는 bean이 각각 다르다(위에서 만든 egovHandler bean과  otherHandler bean을 따로 참조하고 있다). regExpMatcher property에서 참조하는 bean인 antPathMater는 context-common.xml에서 만들어지는 AntPathMather 클래스 타입의 bean으로 이 bean은 나중에 context-common.xml 파일을 Java Config 방식으로 바꿀때 설정되어질 것이다. 일단은 antPathMater bean의 클래스 타입만 알아두자. 이를 Java Config 방식으로 바꾸어 보면 다음과 같이 바꿀수가 있다.

 

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

@Bean
public DefaultExceptionHandleManager otherExceptionHandleManager(AntPathMatcher antPathMater, EgovSampleOthersExcepHndlr otherHandler){
	DefaultExceptionHandleManager otherExceptionHandleManager = new DefaultExceptionHandleManager();
	otherExceptionHandleManager.setReqExpMatcher(antPathMater);
	otherExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
	otherExceptionHandleManager.setHandlers(new ExceptionHandler[]{otherHandler});
	return otherExceptionHandleManager;
}

 

이전 글에서 언급했던 내용을 되새겨 보면서 이 코드들을 보면 충분히 이해가 될 것이다. 그러면 Java Config로 설정해야 할 bean은 이것만 남게 된다

 

<bean id="exceptionTransfer" class="egovframework.rte.fdl.cmmn.aspect.ExceptionTransfer">
	<property name="exceptionHandlerService">
		<list>
			<ref bean="defaultExceptionHandleManager" />
			<ref bean="otherExceptionHandleManager" />
		</list>
	</property>
</bean>

 

이 exceptionTransfer bean을 설정하기 전에 체크해야 할 부분이 있다. exceptionHandlerService property에 defaultExceptionHandleManager bean과 otherExceptionHandleManager bean을 참조하도록 설정되어 있다. 이것을 Java Config로 바꾼다면 아마 처음엔 이렇게 셋팅할 것이다.

 

@Bean
public ExceptionTransfer exceptionTransfer(DefaultExceptionHandleManager defaultExceptionHandleManager
		, DefaultExceptionHandleManager otherExceptionHandleManager){
	
	ExceptionTransfer exceptionTransfer = new ExceptionTransfer();
	exceptionTransfer.setExceptionHandlerService(new DefaultExceptionHandleManager [] {defaultExceptionHandleManager, otherExceptionHandleManager});
	return exceptionTransfer;
}

 

이전 글에서 언급했다시피 메소드의 파라미터로 참조하고자 하는 bean의 클래스 타입과 같은 객체를 받아 해당 bean을 Injection 받을 수 있다. 근데 문제는 이 참조되고자 하는 bean들이 내부적으로 설정값이 다를 뿐 같은 클래스 타입이란 것이다. 위에서 Java Code로 만든 defaultExceptionHandleManager와 otherExceptionHandleManager bean이 모두 DefaultExceptionHandleManager 타입인 것이다. 이렇게 같은 타입이지만 내부적인 설정값이 서로 달라 서로 다른 bean으로 정의된 그러한 bean을 받을때 과연 이것을 어떻게 구분지어서 받아야 할 것인가? 이러한 경우엔 @Autowired 사용시 특정 이름의 bean을 Injection 받기 위해 사용하는 어노테이션인 @Qualifier 어노테이션을 사용하면 된다.

 

@Bean
public ExceptionTransfer exceptionTransfer(@Qualifier("defaultExceptionHandleManager") DefaultExceptionHandleManager defaultExceptionHandleManager
		, @Qualifier("otherExceptionHandleManager") DefaultExceptionHandleManager otherExceptionHandleManager){
	
	ExceptionTransfer exceptionTransfer = new ExceptionTransfer();
	exceptionTransfer.setExceptionHandlerService(new DefaultExceptionHandleManager [] {defaultExceptionHandleManager, otherExceptionHandleManager});
	return exceptionTransfer;
}

 

이위의 Java Code는 이전에 언급했던 exceptionTransfer bean의 Java Code와 내부적인 메소드 코드는 동일하다. 그러나 파라미터를 이용해서 Injection 받는 부분에 있어서 특정 이름의 bean을 받기 위해 사용하는 @Qualifier 어노테이션을 이용하여 같은 클래스 타입의 bean이라 하더라도 이름이 다른 bean을 서로 구분해서 받을수가 있게 된다.

 

이제 남은것은 이렇게 정의된 bean들을 이용하는 실질적인 AOP 설정만이 남게 된다.

 

<aop:config>
	<aop:pointcut id="serviceMethod" expression="execution(* egovframework.example..impl.*Impl.*(..))" />

	<aop:aspect ref="exceptionTransfer">
		<aop:after-throwing throwing="exception" pointcut-ref="serviceMethod" method="transfer" />
	</aop:aspect>
</aop:config>

 

이 글에서는 Spring AOP에 대한 구체적인 설명은 하지 않는다. 위의 설정된 내용을 해석하면 egovframework.example 패키지의 자손(바로 아래단계를 의미하는 것이 아니지 때문에 자식이란 표현을 사용하지 않았다. 자식과 자손의 차이를 이해한다면 이게 무슨 뜻인지 알 수 있으리라 생각한다) 패키지로 impl이란 패키지가 있고 그 패키지에 속하는 클래스로 클래스 이름의 끝이 Impl로 끝나는 모든 클래스의 모든 메소드(파라미터 갯수가 0개 이상이며 어떠한 타입의 어떠한 이름의 파라미터여도 관계없다)가 AOP의 대상이 되며 이 메소드들이 예외를 던진 후에 이름이 exceptionTransfer인 bean의 transfer 메소드를 실행하는 그런 AOP 설정이라 보면 된다.

 

그러면 이제 Java Config로 AOP 설정하는 방법에 대해 알아보자. XML을 이용해서 설정하는 AOP 설정 중에 다음의 태그를 본 적이 있을 것이다. 

 

<aop:aspectj-autoproxy />

 

이 태그의 의미는 Spring에서 AOP 구현시 AOP를 구현하는 대표적인 프레임워크 중 하나인 AspectJ 스타일로 AOP를 구현한다는 의미이다. 부연설명을 좀더 하면 Spring이 구현하는 AOP는 프록시를 이용해서 구현하는 스타일이지만 AspectJ는 Java 클래스의 Byte Code를 조작하여 AOP를 구현하게 된다. 흔히 오해하는 것중의 하나가 위의 태그를 사용하면 AspectJ 프레임워크가 사용하는 방식인 Java 클래스의 Byte Code를 조작하는 방식으로 Spring의 AOP가 구현된다고 생각하는데 절대 아니다. AspectJ에서 AOP를 구현하는데 이용되는 문법을 사용하여 AOP를 구현한다는 것이지 내부적으로는 프록시 방식으로 AOP를 구현하는 것이다. Java Config를 이용하는 AOP의 설정은 이 AspectJ 문법을 이용하는 AOP 설정을 하게 되며 이를 하기 위해서는 Java Config 클래스 정의시 다음의 어노테이션이 설정되어 있어야 한다

 

@EnableAspectJAutoProxy

 

@EnableAspectJAutoProxy 어노테이션엔 속성으로 proxyTargetClass란 것이 있다. 이 속성에 대해 설명을 하자면 Spring은 AOP를 구현하는데 있어서 Proxy를 이용해서 AOP를 구현한다. 근데 AOP의 대상이 되는 클래스가 interface를 구현하고 있는 경우 Spring은 interface를 이용하는 JDK Dynamic Proxy를 만들어서 AOP를 구현하고 interface를 일절 구현하고 있지 않은 클래스에 대해서는 CG-LIB를 이용하여 대상 클래스의 서브클래스를 만들어 이를 Proxy로 삼아 구현하게 된다. 이 속성을 주지 않았을 경우 기본값은 false이기 때문에 대상이 되는 클래스가 interface를 구현하고 있는 상황이면 JDK Dynamic Proxy를 이용한 AOP를, interface를 구현하고 있지 않은 클래스라면 CG-LIB를 이용한 클래스 Proxy를 이용한 AOP를 이용하게 되지만, 이 속성을 true로 줄 경우엔 interface를 구현하고 있는 클래스라 해도 무조건 CG-LIB를 이용한 클래스 Proxy로 AOP를 구현하게 된다. 이 속성은 나중에 설명할 Transaction 관련 설정에서도 다시 언급이 되는데 이유는 Spring의 트랜잭션 또한 AOP를 통해 구현되기 때문이다. 그럼 실제로 이렇게 동작이 되는지 확인해보도록 하자

 

아직 코딩이 완전한 상태는 아니어서 이 글을 보는 여러분들이 직접 테스트 하기는 어려울수도 있겠으나 지금은 그림으로 확인만 해보고 나중에 직접 이 글에서 언급한 방법으로 테스트를 해보기를 바란다. @EnableAspectJAutoProxy 어노테이션을 사용하면서 proxyTargetClass 속성을 사용하지 않은 상태에서 egovframework.example.sample.web.EgovSampleController 클래스의 selectSampleList 메소드의 특정 라인에 중단점을 걸은뒤에 프로젝트를 서버에서 실행시키고 웹브라우저에서 http://localhost:8080/sample로 접근을 해보자. 그러면 전자정부 프레임워크에서 Debug Perspective를 보여주게 되는데 이 Debug Perspective의 우측 상단에 있는 Variables 탭을 클릭하면 다음과 같은 내용을 볼 수 있다.

 

 

 

EgovSampleController 클래스의 EgovSampleService interface 타입의 멤버변수인 sampleService에 는 Spring DI를 통해 EgovSampleService interface를 구현한 EgovSampleServiceImpl 클래스 객체가 Injection이 된다. 그러나 EgovSampleServiceImpl 클래스는 조금 이따가 설명하게 될 @Aspect를 통해 설정한 AOP의 대상 클래스가 되기 때문에 EgovSampleServiceImpl 클래스가 Injection이 되는게 아니라 이 클래스를 접근하게 되는 Proxy가 Injection이 된다. 이때 이 Proxy가 JdkDynamicAopProxy로 Injection이 된 것을 주목해야 한다. 즉 proxyTargetClass 속성을 사용하지 않았기 때문에 interface를 구현한 클래스일 경우 JDK Dynamic Proxy를 이용한 Proxy 클래스가 Injection이 된 것이다.

 

그러나 만약 @EnableAspectJAutoProxy 어노테이션에 proxyTargetClass 속성을 true로 주고 같은 방법으로 테스트 해보면 위의 화면은 다음과 같이 나타난다.

 

 

 

proxyTargetClass 속성을 true로 주었기 때문에 AOP 대상이 되는 클래스가 interface를 구현한 클래스라 하더라도 강제로 CG-LIB를 이용한 Proxy 클래스가 Injection이 되는 것을 볼 수 있다. 

 

AspectJ 스타일로 AOP를 구현할때는 @Aspect 어노테이션을 붙인 클래스를 만들어서 이 클래스에서 수행해야 할 기능을 정의한 Advice와 이 Advice가 어느 시점에 실행이 되어야 하는지가 정의된 Pointcut이 정의되어야 한다. 그리고 이 클래스가 Spring Bean으로 등록이 되어 있어야 한다. 위에서 설정한 AOP XML을 @Aspect 어노테이션이 붙은 클래스로 바꾸면 다음과 같이 된다

 

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import egovframework.rte.fdl.cmmn.aspect.ExceptionTransfer;

@Aspect
public class AopExceptionTransfer {

	private ExceptionTransfer exceptionTransfer;

	public void setExceptionTransfer(ExceptionTransfer exceptionTransfer) {
		this.exceptionTransfer = exceptionTransfer;
	}
	
	@Pointcut("execution(* egovframework.example..impl.*Impl.*(..))") 
	private void exceptionTransferService() {}
	
	@AfterThrowing(pointcut = "exceptionTransferService()", throwing="ex")
	public void doAfterThrowingExceptionTransferService(JoinPoint thisJoinPoint, Exception ex) throws Exception{
		exceptionTransfer.transfer(thisJoinPoint, ex);
	}
}

 

XML로 정의한 AOP 설정이 ExceptionTransfer 클래스 bean을 설정하여 이 bean에 있는 transfer 메소드를 실행하는 것이기 때문에 @Aspect 어노테이션이 붙은 클래스 또한 이 ExceptionTransfer 클래스 bean을 Injection 받을 수 있도록 해야 한다. 그래서 멤버변수로 ExceptionTransfer 클래스 변수를 하나 만든뒤에 이 변수의 setter 메소드로 이 클래스 객체가 멤버변수에 셋팅이 되는 식으로 Injection이 되도록 했다. 그리고 @Pointcut 어노테이션을 이용해서 Pointcut을 정의한다. XML로 정의한 AOP 설정을 보면 Pointcut으로 * egovframework.example..impl.*Impl.*(..) 표현식에 적합한 메소드를 실행하는 시점으로 잡았기 때문에 execution을 이용하여 이를 정의했다. @Pointcut 어노테이션이 붙은 메소드의 역할은 메소드의 선언부를 Pointcut의 이름과 파라미터 정보만을 기록하는 메타정보로 이용되기 때문에 메소드 내부에 코드를 작성할 필요가 없다. Advice는 예외가 던져진 뒤의 작업을 정의하는 Advice이기 때문에 @AfterThrowing 어노테이션을 붙여서 이 어노테이션에 대상이 되는 Pointcut과 던져지는 예외를 받을 파라미터 이름(ex)를 정의한뒤 Advice 역할을 하는 메소드의 파라미터로 JoinPoint 객체와 던져지는 예외 객체를 넣어주어 ExceptionTransfer 클래스 bean의 transfer 메소드를 실행하게끔 했다. 위에서 언급했던 <aop:config> 설정 내용과 이 클래스 소스를 같이 짜맞춰보면 이해하는데 무리는 없으리라 생각한다. 그리고 이렇게 만든 @Aspect 어노테이션이 붙은 클래스를 Spring Bean으로 설정하는 코드를 @Bean 어노테이션이 붙은 메소드로 만들어준다

 

@Bean
public AopExceptionTransfer aopExceptionTransfer(ExceptionTransfer exceptionTransfer){
	AopExceptionTransfer aopExceptionTransfer = new AopExceptionTransfer();
	aopExceptionTransfer.setExceptionTransfer(exceptionTransfer);
	return aopExceptionTransfer;
}

 

위에서 설명한 내용중 ExceptionTransfer 클래스 Bean을 Injection 받는 것에 대한 내용이 있었다. 이 부분을 구현하기 위해 ExceptionTransfer 클래스 객체를 파라미터로 받는 메소드로 정의한 뒤 AopExceptionTransfer 클래스의 setExceptionTransfer 메소드를 이용해서 Injection이 되도록 구현했다. 

 

이로서 context-aspect.xml의 Java Config 변환이 모두 끝났다. 해당 클래스 파일은 ContextAspect란 이름으로 정의되어 있다. 이 클래스의 전체 소스를 보여주는 것으로 이번 글을 마무리하겠다

 

package egovframework.example.config.root;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.util.AntPathMatcher;

import egovframework.example.cmmn.EgovSampleExcepHndlr;
import egovframework.example.cmmn.EgovSampleOthersExcepHndlr;
import egovframework.example.config.root.aop.AopExceptionTransfer;
import egovframework.rte.fdl.cmmn.aspect.ExceptionTransfer;
import egovframework.rte.fdl.cmmn.exception.handler.ExceptionHandler;
import egovframework.rte.fdl.cmmn.exception.manager.DefaultExceptionHandleManager;

@Configuration
@EnableAspectJAutoProxy
public class ContextAspect {

	@Bean
	public AopExceptionTransfer aopExceptionTransfer(ExceptionTransfer exceptionTransfer){
		AopExceptionTransfer aopExceptionTransfer = new AopExceptionTransfer();
		aopExceptionTransfer.setExceptionTransfer(exceptionTransfer);
		return aopExceptionTransfer;
	}
	
	@Bean
	public ExceptionTransfer exceptionTransfer(@Qualifier("defaultExceptionHandleManager") DefaultExceptionHandleManager defaultExceptionHandleManager
			, @Qualifier("otherExceptionHandleManager") DefaultExceptionHandleManager otherExceptionHandleManager){
		
		ExceptionTransfer exceptionTransfer = new ExceptionTransfer();
		exceptionTransfer.setExceptionHandlerService(new DefaultExceptionHandleManager [] {defaultExceptionHandleManager, otherExceptionHandleManager});
		return exceptionTransfer;
	}
	
	@Bean
	public DefaultExceptionHandleManager defaultExceptionHandleManager(AntPathMatcher antPathMater, EgovSampleExcepHndlr egovHandler){
		DefaultExceptionHandleManager defaultExceptionHandleManager = new DefaultExceptionHandleManager();
		defaultExceptionHandleManager.setReqExpMatcher(antPathMater);
		defaultExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
		defaultExceptionHandleManager.setHandlers(new ExceptionHandler[]{egovHandler});
		return defaultExceptionHandleManager;
	}
	
	@Bean
	public DefaultExceptionHandleManager otherExceptionHandleManager(AntPathMatcher antPathMater, EgovSampleOthersExcepHndlr otherHandler){
		DefaultExceptionHandleManager otherExceptionHandleManager = new DefaultExceptionHandleManager();
		otherExceptionHandleManager.setReqExpMatcher(antPathMater);
		otherExceptionHandleManager.setPatterns(new String[]{"**service.impl.*"});
		otherExceptionHandleManager.setHandlers(new ExceptionHandler[]{otherHandler});
		return otherExceptionHandleManager;
	}
	
	@Bean
	public EgovSampleExcepHndlr egovHandler(){
		EgovSampleExcepHndlr egovSampleExcepHndlr = new EgovSampleExcepHndlr();
		return egovSampleExcepHndlr;
	}
	
	@Bean
	public EgovSampleOthersExcepHndlr otherHandler(){
		EgovSampleOthersExcepHndlr egovSampleOthersExcepHndlr = new EgovSampleOthersExcepHndlr();
		return egovSampleOthersExcepHndlr;
	}
}

 

 

목     차

 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