====== XML 스키마 기반 AOP 지원 ====== ===== 개요 ===== Java 5 버전을 사용할 수 없거나, XML 기반 설정을 선호한다면, Spring 2.0 이상에서 제공하는 XML 스키마 기반의 AOP를 사용할 수 있다. Spring은 관점(Aspect) 정의를 지원하기 위해 "aop" 네임스패이스를 제공한다. @AspectJ 를 이용한 AOP 지원에서 사용된 포인트컷 표현식과 충고(Advice) 유형은 XML 스키마 기반 AOP 지원에도 동일하게 제공된다. ===== 설명 ===== ==== 관점(Aspect) 정의하기 ==== Spring 어플리케이션 컨텍스트에서 빈으로 정의된 일반 Java 개체는 관점(Aspect)으로 정의될 수 있다. 관점(Aspect)은 <aop:aspect> 요소를 사용하여 정의한다. <code xml> <bean id="adviceUsingXML" class="org.egovframe.rte.fdl.aop.sample.AdviceUsingXML" /> <aop:config> <aop:aspect ref="adviceUsingXML"> ... </aop:aspect> </aop:config> </code> 관점(Aspect)로 정의된 aBean은 Spring 빈처럼 설정되고 의존성 주입이 될 수 있다. ==== 포인트컷(Pointcut) 정의하기 ==== 포인트컷은 결합점(Join points)을 지정하여 충고(Advice)가 언제 실행될지를 지정하는데 사용된다. Spring AOP는 Spring 빈에 대한 메소드 실행 결합점만을 지원하므로, Spring에서 포인트컷은 빈의 메소드 실행점을 지정하는 것으로 생각할 수 있다. 다음 예제는 egovframework.rte.fdl.aop.sample 패키지 하위의 Sample 명으로 끝나는 클래스의 모든 메소드 수행과 일치할 'targetMethod' 라는 이름의 pointcut을 정의한다. 포인트컷은 <aop:config> 요소 내에 정의한다. 포인트컷 표현식은 AspectJ 포인트컷 표현 언어와 동일하게 사용할 수 있다. <code java> <aop:config> <aop:pointcut id="targetMethod" expression="execution(* org.egovframe.rte.fdl.aop.sample.*Sample.*(..))" /> </aop:config> </code> ==== 충고(Advice) 정의하기 ==== 충고(Advice)는 관점(Aspect)의 실제 구현체로 포인트컷 표현식과 일치하는 결합점에 삽입되어 동작할 수 있는 코드이다. 충고는 결합점과 결합하여 동작하는 시점에 따라 before advice, after advice, around advice 타입으로 구분된다. @AspectJ를 이용한 AOP와 동일하게 5종류의 충고(Advice)를 지원한다. === Before advice === Before advice는 <aop:aspect> 요소 내에서 <aop:before> 요소를 사용하여 정의한다. 다음은 before 충고를 정의하는 XML의 예제이다. before 충고인 beforeTargetMethod() 메소드는 targetMethod()로 정의된 포인트컷 전에 수행된다. <code xml> <aop:aspect ref="adviceUsingXML"> <aop:before pointcut-ref="targetMethod" method="beforeTargetMethod" /> </aop:aspect> </code> 다음은 before 충고를 구현하고 있는 클래스이다. before 충고를 수행하는 beforeTargetXML()메소드는 해당 포인트컷을 가진 클래스명과 메소드명을 출력한다. <code java> public class AdviceUsingXML { ... public void beforeTargetMethod(JoinPoint thisJoinPoint) { System.out.println("AdviceUsingXML.beforeTargetMethod executed."); Class clazz = thisJoinPoint.getTarget().getClass(); String className = thisJoinPoint.getTarget().getClass().getSimpleName(); String methodName = thisJoinPoint.getSignature().getName(); System.out.println(className + "." + methodName + " executed."); } ... } </code> === After returning advice === After returing 충고는 정상적으로 메소드가 실행될 때 수행된다. After 충고는 <aop:aspect> 요소 내에서 <aop:after-returning> 요소를 사용하여 정의한다. 다음은 After returning 충고를 사용하는 예제이다. afterReturningTargetMethod() 충고는 targetMethod()로 정의된 포인트컷 후에 수행된다. targetMethod() 포인트컷의 실행 결과는 retVal 변수에 저장되어 전달된다. <code xml> <aop:aspect ref="adviceUsingXML"> <aop:after-returning pointcut-ref="targetMethod" method="afterReturningTargetMethod" returning="retVal" /> </aop:aspect> </code> 다음은 After returning 충고를 구현하고 있는 클래스이다. After returning 충고를 수행하는 afterReturningTargetMethod()메소드는 해당 포인트컷의 반환값을 출력한다. <code java> public class AdviceUsingXML { ... public void afterReturningTargetMethod(JoinPoint thisJoinPoint, Object retVal) { System.out.println("AdviceUsingXML.afterReturningTargetMethod executed." + return value is [" + retVal + "]"); } ... } </code> === After throwing advice === After throwing 충고는 메소드가 수행 중 예외사항을 반환하고 종료하는 경우 수행된다. After 충고는 <aop:aspect> 요소 내에서 <aop:after-returning> 요소를 사용하여 정의한다. 다음은 After throwing 충고를 사용하는 예제이다. afterThrowingTargetMethod() 충고는 targetMethod()로 정의된 포인트컷에서 예외가 발생한 후에 수행된다. targetMethod() 포인트컷에서 발생된 예외는 exception 변수에 저장되어 전달된다. <code xml> <aop:aspect ref="adviceUsingXML"> <aop:after-throwing pointcut-ref="targetMethod" method="afterThrowingTargetMethod" throwing="exception" /> </aop:aspect> </code> 다음은 After throwing 충고를 구현하고 있는 클래스이다. After throwing 충고를 수행하는 afterReturningTargetMethod()메소드는 전달 받은 예외를 한번 더 감싸서 사용자가 쉽게 알아 볼 수 있도록 메시지를 설정하여 반환한다. <code java> public class AdviceUsingXML { ... public void afterThrowingTargetMethod(JoinPoint thisJoinPoint, Exception exception) throws Exception { System.out.println("AdviceUsingXML.afterThrowingTargetMethod executed."); System.err.println("에러가 발생했습니다.", exception); throw new BizException("에러가 발생했습니다.", exception); } ... } </code> === After (finally) advice === After (finally) 충고는 메소드 수행 후 무조건 수행된다. After 충고는 <aop:aspect> 요소 내에서 <aop:after> 요소를 사용하여 정의한다. After 충고는 다음은 After (finally) 충고를 사용하는 예제이다. afterTargetMethod() 충고는 targetMethod()로 정의된 포인트컷의 정상 종료 및 예외 발생의 경우 모두에 대해 수행된다. 보통은 리소스 해제와 같은 작업을 수행한다. <code xml> <aop:aspect ref="adviceUsingXML"> <aop:after pointcut-ref="targetMethod" method="afterTargetMethod" /> </aop:aspect> </code> 다음은 After (finally) 충고를 구현하고 있는 클래스이다. After (finally) 충고를 수행하는 afterTargetMethod()메소드는 after 충고가 수행됨을 표시하는 메시지를 출력한다. <code java> public class AdviceUsingXML { ... public void afterTargetMethod(JoinPoint thisJoinPoint) { System.out.println("AdviceUsingXML.afterTargetMethod executed."); } ... } </code> === Around advice === Around 충고는 메소드 수행 전후에 수행된다. After 충고는 <aop:aspect> 요소 내에서 <aop:around> 요소를 사용하여 정의한다. Around 충고는 정상 종료와 예외 발생 경우를 모두 처리해야 하는 경우에 사용된다. 리소스 해제와 같은 작업이 해당된다. <code xml> <aop:aspect ref="adviceUsingXML"> <aop:around pointcut-ref="targetMethod" method="aroundTargetMethod" /> </aop:aspect> </code> 다음은 Around 충고를 구현하고 있는 클래스이다. aroundTargetMethod() 충고는 파라미터로 ProceedingJoinPoint을 전달하며 proceed() 메소드 호출을 통해 대상 포인트컷을 실행한다. 포인트컷 수행 결과값인 retVal을 Around 충고 내에서 변환하여 반환할 수 있음을 보여준다. <code java> public class AdviceUsingXML { ... public Object aroundTargetMethod(ProceedingJoinPoint thisJoinPoint) throws Throwable { System.out.println("AdviceUsingXML.aroundTargetMethod start."); long time1 = System.currentTimeMillis(); Object retVal = thisJoinPoint.proceed(); System.out.println("ProceedingJoinPoint executed. return value is [" + retVal + "]"); retVal = retVal + "(modified)"; System.out.println("return value modified to [" + retVal + "]"); long time2 = System.currentTimeMillis(); System.out.println("AdviceUsingXML.aroundTargetMethod end. Time(" + (time2 - time1) + ")"); return retVal; } ... } </code> ==== 관점(Aspect) 실행하기 ==== 앞서 정의한 관점(Aspect)가 정상적으로 동작하는지 확인하기 위해 테스트 코드를 이용해 확인해 본다. AdviceTest 클래스는 대상 메소드 수행시 예외없이 정상 실행하는 경우와 예외 발생의 경우를 구분해서 테스트 한다. === 정상 실행의 경우 === testAdvice() 함수는 대상 메소드가 정상 수행되는 사례를 보여준다. egovframework.rte.fdl.aop.sample 패키지에 속하는 AdviceSample 클래스의 someMethod() 메소드는 before, after returning, after finally, around 충고(Advice)가 적용된다. <code java> public class AdviceTest{ @Resource(name = "adviceSample") AdviceSample adviceSample; @Test public void testAdvice() throws Exception { SampleVO vo = new SampleVO(); .. String resultStr = adviceSample.someMethod(vo); assertEquals("someMethod executed.(modified)", resultStr); } } </code> 테스트 코드를 수행한 결과 로그는 다음과 같다. <code> AdviceUsingXML.beforeTargetMethod executed. AdviceSample.someMethod executed. AdviceUsingXML.aroundTargetMethod start. AdviceUsingXML.afterReturningTargetMethod executed. return value is [someMethod executed.] AdviceUsingXML.afterTargetMethod executed. ProceedingJoinPoint executed. return value is [someMethod executed.] return value modified to [someMethod executed.(modified)] AdviceUsingXML.aroundTargetMethod end. Time(12) </code> 콘솔 로그 출력을 보면 충고(Advice)가 적용되는 순서는 다음과 같다. * @Before * @Around (대상 메소드 수행 전) * 대상 메소드 * @AfterReturning * @After(finally) * @Around (대상 메소드 수행 후) 주의할 점은 @Around 충고는 대상 메소드의 반환 값(return value)를 변경 가능하지만, After returning 충고는 반환 값을 참조 가능하지만 변경할 수 없다. === 예외 발생의 경우 === testAnnotationAspectWithException() 함수는 대상 메소드에 오류가 발생한 사례를 보여준다. egovframework.rte.fdl.aop.sample 패키지에 속하는 AnnotationAdviceSample 클래스의 someMethod() 메소드는 before, after throwing, after finally, around 충고(Advice)가 적용된다. <code java> public class AdviceTest{ @Resource(name = "adviceSample") AdviceSample adviceSample; @Test public void testAdviceWithException() throws Exception { SampleVO vo = new SampleVO(); // exception 을 발생시키도록 플래그 설정 vo.setForceException(true); ... try { // vo 의 forceException 플래그가 true 이면 - / by zero 상황을 강제로 처리함 resultStr = adviceSample.someMethod(vo); fail("exception 을 강제로 발생시켜 이 라인이 수행될 수 없습니다."); } catch (Exception e) { ... } } } </code> 테스트 코드를 수행한 결과 로그는 다음과 같다. <code> AdviceUsingXML.beforeTargetMethod executed. AdviceSample.someMethod executed. AdviceUsingXML.aroundTargetMethod start. AdviceUsingXML.afterThrowingTargetMethod executed. 에러가 발생했습니다. java.lang.ArithmeticException: / by zero ... </code> 콘솔 로그 출력을 보면 충고(Advice)가 적용되는 순서는 다음과 같다. * @Before * @Around (대상 메소드 수행 전) * 대상 메소드 (ArithmeticException 예외가 발생한다) * @AfterThrowing * @After(finally) 예외가 발생하더라도 after 로 정의한 충고(Advice)는 수행되는 것을 확인할 수 있다. After Throwing 충고(Advice)는 에러 메시지를 재설정하고 새로운 예외를 생성하여 전달할 수 있다. ===== 참고자료 ===== * [[https://docs.spring.io/spring-framework/docs/5.3.27/reference/html/core.html#aop-schema|Spring Framework - Reference Document / 5.5 Schema-based AOP support]]