AspectJ Weaver를 사용한 XML 기반의 스프링 AOP 구현 방법

2021. 4. 22. 02:46 Spring Framework/Spring Core

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

인프런 - 소프트캠퍼스 스프링 프레임워크 개발자를 위한 실습을 통한 입문 과정

 

출처 : atoz-develop.tistory.com/entry/AspectJ-Weaver%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-XML-%EA%B8%B0%EB%B0%98%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95?category=869243