Spring AOP
AOP(Aspect Oriented Programming), 관점지향 프로그래밍
<img src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/E0X8AG181224235505.PNG" style="width:400px">
기능을 핵심기능과 공통기능을 분리하고,
공통기능을 필요로하는 핵심기능들에서 사용할 수 있도록 하는 프로그래밍.
<br/>
AOP 관련 용어
-
Joinpoint [조인포인트] : 핵심기능들, 메소드들, 포인트 컷에 후보
-
Pointcut [포인트컷] : 공통기능을 사용하는 특정 조건의 Joinpoint를 선정하는 것.
-
Advice[어드바이스] : 공통기능 코드
-
Weaving[위빙] : Pointcut으로 지정된 핵심 메소드가 호출될때, Advice가 삽입되는 과정
-
Aspect[에스펙트] : Pointcut + Advice. 어떤 Pointcut에 어떤 Advice를 실행할지 결정한다.
<br/>
<br/>
<br/>
AOP 원리
<img src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/BYVUUM181224235505.PNG" style="width:400px">
AOP는 Proxy를 사용하여 구현된다.
핵심기능을 호출하는 Caller의 요청을 Proxy가 가로챈다
Proxy는 핵심기능을 수행하기 전/후로 공통기능을 수행한다.
<br/>
AOP 구현
다음과 같은 클래스 구조의 프로그래밍을 설계하였다.
<img src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/WSDNAL181224235506.PNG" style="width:300px">
sound() 메소드 호출이 있을때, AOP를 통하여 로그를 남겨주려고 한다.
<br/>
우선 Pom.xml에 다음 라이브러리를 추가한다.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<br/>
<br/>
AppContext.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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="Cat" class="com.changoos.study.aop.Cat"></bean>
<bean id="MyAspect" class="com.changoos.study.aop.MyAspect"></bean>
<aop:config>
<aop:aspect ref="MyAspect">
<aop:pointcut expression="execution(void com.changoos.study.aop.*.sound())"
id="pointCut" />
<aop:before method="before" pointcut-ref="pointCut" />
<aop:after method="after" pointcut-ref="pointCut" />
<aop:after-returning method="afterReturning" pointcut-ref="pointCut" />
<aop:around method="around" pointcut-ref="pointCut" />
</aop:aspect>
</aop:config>
</beans>
<br/>
-
11 : Aspect 빈 설정
-
12 : Pointcut 설정, 표현식 하단에 정리
-
15 : Pointcut 메소드 실행전 호출 메소드
-
16 : Pointcut 메소드 실행후 호출 메소드
-
17 : Pointcut 메소드 이상없이 실행 후 호출 메소드
-
18 : Pointcut 메소드 실행전/후 호출 메소드
<br/>
<br/>
MyAspect 클래스를 살펴보면, 호출될 메소드가 정의되어있다.
public class MyAspect {
public void before() {
System.out.println("Before Advice");
}
public void after() {
System.out.println("After Advice");
}
public void afterReturning() {
System.out.println("After-Returning Advice");
}
public void around(ProceedingJoinPoint proceedingJoinPoint) {
try {
long start = System.currentTimeMillis();
proceedingJoinPoint.proceed();
long stop = System.currentTimeMillis();
System.out.println("Around Advice : " + (stop - start));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
<br/>
main 메소드 호출, 결과
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("/com/changoos/study/aop/AppContext.xml");
AnimalType animal = (AnimalType)context.getBean("Cat");
animal.sound();
}
}
<br/>
출력값
12월 04, 2018 7:55:11 오후 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@533ddba: startup date [Tue Dec 04 19:55:11 KST 2018]; root of context hierarchy
12월 04, 2018 7:55:12 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [com/changoos/study/aop/AppContext.xml]
Before Advice
냐옹~~
Around Advice : 0
After-Returning Advice
After Advice
<br/>
<br/>
Annotaion을 이용하여 구현해보자.
대량의 XML 파일을 작성하는 것 또한 단점이다.
외부설정 파일에 매번 일일이 다 작성하지말고, Annotaion을 통하여 구현해보자.
<br/>
AppContext.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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<bean id="Cat" class="com.changoos.study.aop.Cat"></bean>
<bean id="Aspect" class="com.changoos.study.aop.MyAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<br/>
- 10 : Annotation을 통해서 auto로!
<br/>
<br/>
MyAspect 클래스는, Annotaion을 이용하여 다음과 같이 변경하였다.
@Aspect
public class MyAspect {
@Pointcut("execution(void com.changoos.study.aop.*.sound())")
public void myPointcut() {
};
@Before("myPointcut()")
public void before() {
System.out.println("Before Advice");
}
@After("myPointcut()")
public void after() {
System.out.println("After Advice");
}
@AfterReturning("myPointcut()")
public void afterReturning() {
System.out.println("After-Returning Advice");
}
@Around("myPointcut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) {
try {
long start = System.currentTimeMillis();
proceedingJoinPoint.proceed();
long stop = System.currentTimeMillis();
System.out.println("Around Advice : " + (stop - start));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
<br/>
출력값.
12월 04, 2018 8:20:44 오후 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@533ddba: startup date [Tue Dec 04 20:20:44 KST 2018]; root of context hierarchy
12월 04, 2018 8:20:44 오후 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [com/changoos/study/aop/AppContext.xml]
Before Advice
냐옹~~
Around Advice : 1
After Advice
After-Returning Advice
<br/>
<br/>
* Pointcut 표현식 정리
-
execution([수식어] [리턴타입] [클래스이름] 이름
-
execution(void sound*(..))<br/>
리턴타입이 void이고, sound로 시작하는, 파라미터가 0개 이상인 메소드 -
execution(* com.changoos.study.di..())<br/>
리턴타입에 관계없이, 메소드이름도 관계없이, com.changoos.study.di 패키지에있는, 파라미터가 0 개인 메소드 -
execution(* com.changoos.study.di..(..))<br/>
리턴타입에 관계없이, 메소드이름도 관계없이, com.changoos.study.di 패키지에있는, 파라미터가 0 개이상인 메소드 -
execution(* sound*(*))<br/>
리턴타입에 관계없이, 메소드이름이 sound로 시작하는, 매개변수가 1개인 메소드 -
execution(* sound*(,))<br/>
리턴타입에 관계없이, 메소드이름이 sound로 시작하는, 매개변수가 2개인 메소드 -
execution(* sound*(Integer,*))<br/>
리턴타입에 관계없이, 메소드이름이 sound로 시작하는, 매개변수가 2개인, 첫번째 파라미터 타입이 Integer인 메소드<br/> -
within(패키지)
-
within(com.changoos.study.di)<br/>
com.changoos.study.di 패키지의 모든 메소드
<br/>
<br/>