Spring AOP
- Aspect Oriented Programming
Concept
- Scattered duplicated code
- Caching, Logging, Transaction, Security, ...
- Devide Concerns
Terms
- JoinPoint
- Crosscutting Concerns ๋ชจ๋์ด ์ฝ์
๋์ด ๋์ํ ์ ์๋ ์คํ ๊ฐ๋ฅํ ํน์ ์์น
- ๋ฉ์๋ ํธ์ถ, ๋ฉ์๋ ์คํ ์์ฒด, ํด๋์ค ์ด๊ธฐํ, ๊ฐ์ฒด ์์ฑ ์์ ๋ฑ
- Pointcut
- Pointcut์ ์ด๋ค ํด๋์ค์ ์ด๋ JoinPoint๋ฅผ ์ฌ์ฉํ ๊ฒ์ธ์ง๋ฅผ ๊ฒฐ์ ํ๋ ์ ํ ํจํด
- ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ Pointcut์ โํน์ ํด๋์ค์ ์๋ ๋ชจ๋ ๋ฉ์๋ ํธ์ถโ๋ก ๊ตฌ์ฑ
- ์ ์คํํธ(Aspect)
- Advice์ Pointcut์ ์กฐํฉ
- ์ ํ๋ฆฌ์ผ์ด์
์ด ๊ฐ์ง๊ณ ์์ด์ผ ํ ๋ก์ง๊ณผ ๊ทธ๊ฒ์ ์คํํด์ผ ํ๋ ์ง์ ์ ์ ์ํ ๊ฒ
- Advice
- Advice๋ ๊ด์ (Aspect)์ ์ค์ ๊ตฌํ์ฒด๋ก ๊ฒฐํฉ์ ์ ์ฝ์
๋์ด ๋์ํ ์ ์๋ ์ฝ๋
- Advice ๋ ๊ฒฐํฉ์ (JoinPoint)๊ณผ ๊ฒฐํฉํ์ฌ ๋์ํ๋ ์์ ์ ๋ฐ๋ผ before advice, after advice, around advice ํ์
์ผ๋ก ๊ตฌ๋ถ
- ํน์ JoinPoint์ ์คํํ๋ ์ฝ๋
- Weaving
- Pointcut์ ์ํด์ ๊ฒฐ์ ๋ JoinPoint์ ์ง์ ๋ Advice๋ฅผ ์ฝ์
ํ๋ ๊ณผ์
- Weaving์ AOP๊ฐ ๊ธฐ์กด์ Core Concerns ๋ชจ๋์ ์ฝ๋์ ์ ํ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์ ํ์ํ Crosscutting Concerns ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์๊ฒ ํด ์ฃผ๋ ํต์ฌ์ ์ธ ์ฒ๋ฆฌ ๊ณผ์
Simple Example
Spring Boot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Aspect
@Component
public class LoggingAspect {
private long startTime;
@Before("execution(* com.example..check*(..))")
public void onBeforeHandler(JoinPoint joinPoint) {
startTime = System.nanoTime();
System.out.println("start");
}
@After("execution(* com.example..check*(..))")
public void onAfterHandler(JoinPoint joinPoint) {
long endTime = System.nanoTime();
System.out.println("elapsed time: " + (endTime - startTime));
}
@Around("execution(* check*()) && within(com.example..*)")
public String around(ProceedingJoinPoint pjp) throws Throwable {
String returnValue = null;
try {
returnValue = (String) pjp.proceed();
} catch(Exception e) {
// log or re-throw the exception
}
// modify the return value
returnValue = "**" + returnValue + "**";
return returnValue;
}
}
@RestController
public class HotelController {
@GetMapping("/in")
@ResponseBody
public String checkIn() {
return "check-in";
}
@GetMapping("/out")
@ResponseBody
public String checkOut() {
return "check-in";
}
}
Spring Framework 3.x
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.18.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.18.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<?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/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/aop/spring-aop-3.2.xsd ">
<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* com.okdevtv.spring.aop.*.*(..))" />
<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
<aop:after pointcut-ref="selectAll" method="afterAdvice" />
<aop:after-returning pointcut-ref="selectAll"
returning="retVal" method="afterReturningAdvice" />
<aop:after-throwing pointcut-ref="selectAll"
throwing="ex" method="AfterThrowingAdvice" />
</aop:aspect>
</aop:config>
<!-- Definition for student bean -->
<bean id="student" class="com.okdevtv.spring.aop.Student">
<property name="name" value="Zara" />
<property name="age" value="11" />
</bean>
<!-- Definition for logging aspect -->
<bean id="logging" class="com.okdevtv.spring.aop.Logging" />
</beans>
package com.okdevtv.spring.aop;
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age);
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name);
return name;
}
public void printThrowException() {
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}
package com.okdevtv.spring.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}
package com.okdevtv.spring.aop;
public class Logging {
/**
* This is the method which I would like to execute before a selected method
* execution.
*/
public void beforeAdvice() {
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute after a selected method
* execution.
*/
public void afterAdvice() {
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute when any method returns.
*/
public void afterReturningAdvice(Object retVal) {
System.out.println("Returning:" + retVal.toString());
}
/**
* This is the method which I would like to execute if there is an exception
* raised.
*/
public void AfterThrowingAdvice(IllegalArgumentException ex) {
System.out.println("There has been an exception: " + ex.toString());
}
}
<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* com.okdevtv.spring.aop.Student.getName(..))" />
<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
<aop:after pointcut-ref="selectAll" method="afterAdvice" />
</aop:aspect>
</aop:config>
ref