AspectJ Weaver를 사용한 XML 기반의 스프링 AOP 구현 방법
AspectJ Weaver를 사용한 XML 기반의 스프링 AOP 구현 방법
AOP란?
- Aspect Oriented Programming : 관점 지향 프로그래밍
- OOP 와 분리된 개념이 아니라, OOP에 기초를 두는 프로그래밍 방식
- 하나의 프로그램을 관점(혹은 관심사)라는 논리적인 단위로 분리하여 관리하는 개념
- 로깅, 감사, 선언적 트랜젝션, 보안, 캐싱 등 다양한 곳에서 사용된다.
AOP 용어
- Joint Point : 모듈이 삽입되어 동작하게 되는 특정 위치(메서드 호출 등)
- Point Cut : 다양한 Joint Point 중에 어떤 것을 사용할지 선택
- Advice : Joint Point에 삽입되어 동작할 수 있는 코드
- Weaving : Advice를 핵심 로직 코드에 적용하는 것
- Aspect : Point Cut + Advice
Spring AOP Advice 종류
- before : 메서드 호출 전에 동작하는 Advice
- after-returning : 예외 없이 호출된 메서드의 동작이 완료되면 동작하는 Advice
- after-throwing : 호출된 메서드 동작 중 예외가 발생했을 때 동작하는 Advice
- after : 예외 발생 여부에 관계없이 호출된 메서드의 동작이 완료되면 동작하는 Advice
- around : 메서드 호출 전과 후에 동작하는 Advice
Spring AOP 구현
AspectJ Weaver를 사용한 Spring AOP 구현 방법은 XML을 이용하는 것과 @AspectJ 애노테이션을 이용하는 것으로 나뉜다.
본 글에서는 XML 기반의 구현 방법을 살펴본다.
관련 글 - AspectJ Weaver를 사용한 애노테이션 기반의 스프링 AOP 구현 방법
1) 공통 사항
① AspectJ Weaver 라이브러리 추가
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
② 관심사로 설정할 코드 작성
public class TestBean {
public int method1() {
System.out.println("method1 호출");
return 100;
}
}
관심사로 설정할 코드를 작성한다.
TestBean 클래스의 method1() 메소드 호출을 관심사로 설정할 것이다.
다양한 케이스를 테스트하기 위해 int형 데이터를 리턴하도록 하였다.
public class MainClass {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
TestBean bean1 = ctx.getBean("testBean", TestBean.class);
int a1 = bean1.method1();
System.out.printf("a1 : %d\n", a1);
ctx.close();
}
}
main 메소드에서 ApplicationContext 객체를 생성하고 TestBean 빈 객체를 가져온 뒤 TestBean 클래스의 method1() 메소드를 호출한다.
🖥 실행 결과
method1 호출
a1 : 100
Spring AOP를 사용해 method1()의 호출을 관심사로 설정하여 method1() 호출 전, 후에 특정 메소드를 호출할 수 있도록 구현할 것이다.
③ Advisor 클래스 생성
public class AdvisorClass {
}
Advice들을 모아놓은 것을 Advisor라고 한다.
이 클래스에 method1()의 호출 전, 후에 호출할 메소드를 구현할 것이다.
2) XML을 이용한 구현 방법
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
</aop:aspect>
</aop:config>
</beans>
스프링 xml 설정에 aop 네임스페이스를 추가하고 TestBean, AdvisorClass 클래스를 빈으로 등록한다.
<aop:config>로 AOP 설정을 한다.
<aop:aspect>의 ref 속성에 advisor 클래스의 빈 id를 지정한다.
<aop:pointcut>의 expression에 execution 명시자를 사용해서 특정 관심사를 지정한다.
execution(* method1())과 같이 설정하면 모든 패키지, 모든 클래스의 method1() 메소드 호출이 관심사가 된다.
🔗 참고 - [AspectJ Weaver] execution 지시자 설정 방법 정리
① Before - 메소드 호출 전에 동작
public class AdvisorClass {
// before advice
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
}
advisor 클래스에 메소드 호출 전에 호출할 메소드를 구현한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
<!-- before -->
<aop:before method="beforeMethod" pointcut-ref="point1"/>
</aop:aspect>
</aop:config>
</beans>
<aop:before>로 before advice를 설정한다.
method에 advisor 클래스에 구현한 메소드의 이름을, pointcut-ref에 위에서 설정한 <aop:pointcut>의 id를 지정한다.
이제 method1() 호출에 대한 관심사 설정, before advice 구현 및 설정이 완료되었다.
🖥 실행 결과
beforeMethod 호출
method1 호출
a1 : 100
② After Advice - 예외 발생 여부에 관계없이 호출된 메소드의 동작이 완료되면 동작
public class AdvisorClass {
// before advice
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
// after advice
public void afterMethod() {
System.out.println("afterMethod 호출");
}
}
advisor 클래스에 after advice 메소드를 구현한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
<!-- before -->
<aop:before method="beforeMethod" pointcut-ref="point1"/>
<!-- after -->
<aop:after method="afterMethod" pointcut-ref="point1"/>
</aop:aspect>
</aop:config>
</beans>
<aop:after>로 after advice를 설정한다.
설정 방법은 <aop:before>와 동일하다.
🖥 실행 결과
beforeMethod 호출
method1 호출
afterMethod 호출
a1 : 100
③ Around Advice - 메소드 호출 전과 후에 동작
import org.aspectj.lang.ProceedingJoinPoint;
public class AdvisorClass {
// before advice
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
// after advice
public void afterMethod() {
System.out.println("afterMethod 호출");
}
// around advice
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("aroundMethod 호출1");
// 원래의 메소드를 호출한다.
Object obj = pjp.proceed();
System.out.println("aroundMethod 호출 2");
return obj;
}
}
advisor 클래스에 around adivce 메소드를 구현한다.
이 때는 org.aspectj.lang.ProceedingJoinPoint를 메소드의 인자로 받아서 이 객체의 proceed()를 호출하여 작업 중간에 원래 메소드를 호출하는 방식으로 구현한다.
원래 메소드가 리턴값이 있을 경우 Object 타입으로 받아서 반환한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
<!-- before -->
<aop:before method="beforeMethod" pointcut-ref="point1"/>
<!-- after -->
<aop:after method="afterMethod" pointcut-ref="point1"/>
<!-- around -->
<aop:around method="aroundMethod" pointcut-ref="point1"/>
</aop:aspect>
</aop:config>
</beans>
<aop:around>로 after advice를 설정한다.
🖥 실행 결과
beforeMethod 호출
aroundMethod 호출1
method1 호출
aroundMethod 호출 2
afterMethod 호출
a1 : 100
④ After-Returning Advice - 예외 없이 호출된 메소드의 동작이 완료되면 동작
import org.aspectj.lang.ProceedingJoinPoint;
public class AdvisorClass {
// before advice
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
// after advice
public void afterMethod() {
System.out.println("afterMethod 호출");
}
// around advice
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("aroundMethod 호출1");
// 원래의 메서드를 호출한다.
Object obj = pjp.proceed();
System.out.println("aroundMethod 호출 2");
return obj;
}
// after-returning advice
public void afterReturningMethod() {
System.out.println("afterReturningMethod 호출");
}
}
After advice 메소드 구현 방법은 before, after와 다르지 않다.
매개변수 없이 수행할 작업을 구현하면 된다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
<!-- before -->
<aop:before method="beforeMethod" pointcut-ref="point1"/>
<!-- after -->
<aop:after method="afterMethod" pointcut-ref="point1"/>
<!-- around -->
<aop:around method="aroundMethod" pointcut-ref="point1"/>
<!-- after-returning -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="point1"/>
</aop:aspect>
</aop:config>
</beans>
<aop:after-returning>으로 설정한다.
🖥 실행 결과
beforeMethod 호출
aroundMethod 호출1
method1 호출
afterReturningMethod 호출
aroundMethod 호출 2
afterMethod 호출
a1 : 100
⑤ After-Throwing Advice - 호출된 메서드 동작 중 예외가 발생했을 때 동작
import org.aspectj.lang.ProceedingJoinPoint;
public class AdvisorClass {
// before advice
public void beforeMethod() {
System.out.println("beforeMethod 호출");
}
// after advice
public void afterMethod() {
System.out.println("afterMethod 호출");
}
// around advice
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("aroundMethod 호출1");
// 원래의 메서드를 호출한다.
Object obj = pjp.proceed();
System.out.println("aroundMethod 호출 2");
return obj;
}
// after-returning advice
public void afterReturningMethod() {
System.out.println("afterReturningMethod 호출");
}
// after-throwing advice
public void afterThrowingMethod(Throwable e1) {
System.out.println("afterThrowingMethod 호출");
System.out.println(e1);
}
}
After-throwing advice 메소드는 발생한 예외를 메소드 아규먼트로 받아 예외 정보를 출력하는 등의 추가 작업을 할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 빈 등록 -->
<bean id='testBean' class='com.atoz_develop.beans.TestBean'/>
<bean id='advisorClass' class='com.atoz_develop.advisor.AdvisorClass'/>
<!-- AOP 설정 -->
<aop:config>
<aop:aspect ref='advisorClass'>
<aop:pointcut id="point1" expression="execution(* method1())"/>
<!-- before -->
<aop:before method="beforeMethod" pointcut-ref="point1"/>
<!-- after -->
<aop:after method="afterMethod" pointcut-ref="point1"/>
<!-- around -->
<aop:around method="aroundMethod" pointcut-ref="point1"/>
<!-- after-returning -->
<aop:after-returning method="afterReturningMethod" pointcut-ref="point1"/>
<!-- after-throwing -->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="point1" throwing="e1"/>
</aop:aspect>
</aop:config>
</beans>
after-throwing advice는 <aop:after-throwing>으로 설정하는데, 예외 객체를 after-throwing advice 메소드에서 받을 수 있도록 throwing 속성을 사용해서 메소드의 예외 아규먼트 이름을 지정한다.
public class TestBean {
public int method1() {
System.out.println("method1 호출");
int a1 = 10 / 0; // 예외 발생
return 100;
}
}
테스트를 위해 method1()에서 예외를 발생시키는 코드를 추가한다.
🖥 실행 결과
beforeMethod 호출
aroundMethod 호출1
method1 호출
afterThrowingMethod 호출
java.lang.ArithmeticException: / by zero
afterMethod 호출
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.atoz_develop.beans.TestBean.method1(TestBean.java:8)
at com.atoz_develop.beans.TestBean$$FastClassBySpringCGLIB$$13abe977.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
at com.atoz_develop.advisor.AdvisorClass.aroundMethod(AdvisorClass.java:22)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:47)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.atoz_develop.beans.TestBean$$EnhancerBySpringCGLIB$$b59cccdc.method1(<generated>)
at com.atoz_develop.main.MainClass.main(MainClass.java:15)
Process finished with exit code 1
관심사로 설정한 method1() 호출 중 예외가 발생하자 after-returning advice 메소드 대신 after-throwing advice 메소드가 호출되는 것을 볼 수 있다.
테스트 후 method1()에서 예외를 발생시키기 위해 추가한 코드를 삭제하는 것을 잊지 말자.
관련 글
AspectJ Weaver를 사용한 애노테이션 기반의 스프링 AOP 구현 방법
[AspectJ Weaver] execution 지시자 설정 방법 정리
References
인프런 - 소프트캠퍼스 스프링 프레임워크 개발자를 위한 실습을 통한 입문 과정
'Spring Framework > Spring Core' 카테고리의 다른 글
[Spring] Interceptor 웹 요청처리시간을 측정 (0) | 2022.01.25 |
---|---|
[Spring 프로젝트] Interceptor로 request, response body json 값 로깅하기 (0) | 2022.01.24 |
[Spring] Filter, Interceptor, AOP 차이 및 정리 (0) | 2022.01.24 |
AspectJ Weaver를 사용한 애노테이션 기반의 스프링 AOP 구현 방법 (0) | 2021.04.22 |
[Spring] @Component 애노테이션 및 함께 사용하는 애노테이션 정리 (0) | 2021.04.22 |
[Spring] 애노테이션을 이용한 빈 설정 방법 정리 (0) | 2021.04.22 |
[Spring] 스프링 XML 설정 → 애노테이션 설정 변환 방법 (0) | 2021.04.22 |
[Spring] 스프링 빈(Bean) 초기화, 소멸 시 특정 작업을 하는 방법 (0) | 2021.04.22 |