목표 : Spring에서 AOP를 사용할 수 있다.
앞서, AOP가 무엇인지에 대해 알아보았다.
이제 Spring에서 AOP를 어떻게 쓸 수 있을지 알아보자.
Aspect Oriented Programming with Spring
다음 링크에서 Spring에서 AOP에 대한 적용은 다음과 같이 할 수 있다고 한다.
Spring provides simple and powerful ways of writing custom aspects by using either a schema-based approach or the @AspectJ annotation style. Both of these styles offer fully typed advice and use of the AspectJ pointcut language while still using Spring AOP for weaving.
즉, schema-based approach를 사용할 수도 있고, @AspectJ라는 Annotation을 사용할수도 있다고 한다.
Schema-based AOP Support
Annotation은 소스가 분산되어, 이해를 돕기위해 Schema-based 방식을 먼저 보도록 하자.
먼저, XML로 AOP를 설정하려면 aop Schema를 설정해주어야 한다.
Declaring an Aspect
그럼 Aspect부터 설정해보자.
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
</aop:aspect>
</aop:config>
<bean id="aBean" class="com.example.demo.aop.AopBean">
</bean>
</beans>
예시에 따라 구성하였고, AopBean 클래스를 만들어, Bean 설정에 추가해주자.
Declaring a Pointcut
Pointcut도 마찬가지로 설정할 수 있다.
Pointcut은 aspect 안에 존재한다.
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
바로 작성하기 전에, 위 예시를 보면, expression이 존재한다.
execution(* com.xyz.myapp.service.*.*(..))
excution 할 때 (Runtime 때) 특정 패키지(com.xyz...service)의 모든 클래스(service.*), 모든 메서드(service.*.*), 그리고 모든 Argument(*.*.(..))에 해당한다는 표현식이다.
그럼 실제로 적용해보자.
package com.example.demo.aop;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AopBean {
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService" expression="execution(* com.example.demo.aop.AopBean.*.*(..))"/>
</aop:aspect>
</aop:config>
<bean id="aBean" class="com.example.demo.aop.AopBean">
</bean>
</beans>
Declaring Advice
이제 Advice를 적용해보자.
package com.example.demo.aop;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AopBean {
public void beforeLog() {
log.error(">>> before aop log");
}
public void afterLog() {
log.error(">>> after aop log");
}
public void afterReturningLog() {
log.error(">>> afterReturningLog aop log");
}
public void afterThrowingLog() {
log.error(">>> afterThrowingLog aop log");
}
}
package com.example.demo.aop;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Service {
public void log() {
log.error(">>> aop log");
}
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService" expression="execution(* com.example.demo.aop.Service.*(..))"/>
<aop:before pointcut-ref="businessService" method="beforeLog"/>
<aop:after pointcut-ref="businessService" method="afterLog"/>
<aop:after-returning pointcut-ref="businessService" method="afterReturningLog"/>
<aop:after-throwing pointcut-ref="businessService" method="afterThrowingLog"/>
</aop:aspect>
</aop:config>
<bean id="aBean" class="com.example.demo.aop.AopBean">
</bean>
<bean id="service" class="com.example.demo.aop.Service">
</bean>
</beans>
package com.example.demo.aop;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Service service = context.getBean(Service.class);
service.log();
context.close();
}
}
혹시, Class Not Found 에러가 난다면, 아래 2개 Dependency를 추가해주자. (Springboot는 starter-aop 가 따로 있다)
04:39:29.454 [main] ERROR com.example.demo.aop.AopBean - >>> before aop log
04:39:29.466 [main] ERROR com.example.demo.aop.Service - >>> aop log
04:39:29.466 [main] ERROR com.example.demo.aop.AopBean - >>> after aop log
04:39:29.466 [main] ERROR com.example.demo.aop.AopBean - >>> afterReturningLog aop log
그 결과를 위와 같이 확인해 볼 수 있다.
Around Advice
그럼, 위에서 했던 것들을 한번에 해주는 Around Advice를 보자.
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
Around에서는 method에서 Argument를 받는다.
앞선 포스팅에서 확인해 보았듯이, Proxy로 Sample 예제를 보았을 때와 동일하다.
package com.example.demo.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
@Slf4j
public class AopBean {
public void aroundLog(ProceedingJoinPoint pjp) {
log.error(">>> before aop log");
try {
Object proceed = pjp.proceed();
log.error(">>> returning aop log");
} catch (Throwable throwable) {
log.error(">>> throwing aop log");
}
log.error(">>> after aop log");
}
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- bean definitions here -->
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService" expression="execution(* com.example.demo.aop.Service.*(..))"/>
<aop:around pointcut-ref="businessService" method="aroundLog"/>
</aop:aspect>
</aop:config>
<bean id="aBean" class="com.example.demo.aop.AopBean">
</bean>
<bean id="service" class="com.example.demo.aop.Service">
</bean>
</beans>
@AspectJ
그럼 이제, Annotation을 통해, 처리하는 것으로 변경해보도록 하자.
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AppConfig {
}
먼저, AspectJ는 외부 라이브러리이므로, Configuration에 위와 같이 넣어줄 필요가 있다.
@Slf4j
@Component
public class Service {
public void log() {
log.error(">>> aop log");
}
}
@Slf4j
@Aspect
@Component
public class AopBean {
@Pointcut("execution(* com.example.demo.aop.Service.*(..))")
public void aopPointcut(){}
@Around("aopPointcut()")
public Object aroundLog(ProceedingJoinPoint pjp) {
log.error(">>> before aop log");
try {
Object proceed = pjp.proceed();
log.error(">>> returning aop log");
return proceed;
} catch (Throwable throwable) {
log.error(">>> throwing aop log");
}
log.error(">>> after aop log");
return null;
}
}
package com.example.demo.aop;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
Service service = context.getBean(Service.class);
service.log();
context.close();
}
}
XML 파일을 없애더라도, 정상 동작하는 것을 알 수 있다.
@Aspect 안에, Pointcut을 설정하였고, @Around로 Advice도 적용되었다는 것을 알 수 있다.
상황에 따라, Pointcut을 별도로 정리하고, Advice에는 여러 Pointcut을 적용하는 등의 동작도 가능하다.
Wrap Up
앞서 배운 지식과 합쳐서, AOP를 Spring에서 어떻게 사용하는지에 대해 알아볼 수 있었다.
'Framework > Spring' 카테고리의 다른 글
[Spring] DispatcherServlet이 뭘까? (0) | 2022.08.16 |
---|---|
[Spring] Bean의 주입은 어떻게 하는게 좋을까? (0) | 2022.08.14 |
[Spring] AOP는 뭘까? (0) | 2022.08.13 |
[Spring] Validation은 어떻게 확인할까? (0) | 2022.08.12 |
[Spring] Resource는 어떻게 처리할까? (0) | 2022.08.11 |