✔️ 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하는 것
관점 지향 프로그래밍
횡단 관심사(cross-cutting concerns)
부가 기능을 동적으로 추가해주는 기술
: 동적으로 추가한다는 말은, 우리가 프로그램 만들때 만드는 것이 아니라, 코드가 수행될 떄 자동으로 추가해준다.
➡️ 메서드의 시작 또는 끝에 자동으로 코드(advice
)를 추가
AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다.
위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지
하나의 메서드에 서로 다른 관심사가 있다.
: 핵심기능과 기능이 서로 다른 관심사이다. 그 둘을 분리한다.
로깅, 트랜잭션, 시큐리티는 공통 관심사여서 저렇게 표현한다. 그래서 횡단 관심사라고도 말한다.
횡단 관심사는 모듈별로 따로 추가해주기 보다는 advice로 별도로 만들어서 동적으로 추가해주는 기술이 aop이다.
프록시 패턴 기반의 AOP 구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서임
스프링 빈에만 AOP를 적용 가능
모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 엔터프라이즈 애플리케이션에서 가장 흔한 문제(중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 ...)에 대한 해결책을 지원하는 것이 목적
advice + target = Proxy
: 실행중에 target안에 advice가 필요한 곳으로 들어가게 된다. 마치 원래 있던 것처럼 합쳐진다.
: advice와 target은 원래 하나여야 하는 것들인데 분리 시켜놨다. 공통 코드니까 분리해 놓았다가 실행중에 다시 합친다. ➡️ 새로운 객체가 만들어짐
합치는 기능 ➡️ weaving
package kr.ac.jipark09.aop;
import java.lang.reflect.Method;
public class AopMain {
public static void main(String[] args) throws Exception {
MyAdvice myAdvice = new MyAdvice();
// MyClass의 객체를 생성해서 MyAdvice의 invoke메서드에 넘겨줄 것이다.
Class myClass = Class.forName("kr.ac.jipark09.aop.MyClass");
Object obj = myClass.newInstance();
for (Method m : myClass.getDeclaredMethods()) {
myAdvice.invoke(m, obj, null);
}
}
}
// 공통 코드를 메서드로 만들고 각각 호
class MyAdvice {
void invoke(Method m, Object obj, Object ... args) throws Exception {
System.out.println("[before]{");
m.invoke(obj, args); // aaa(), bbb(). ccc()
System.out.println("}[after]");
}
}
class MyClass {
void aaa() {
System.out.println("aaa");
}
void bbb() {
System.out.println("bbb");
}
void ccc() {
System.out.println("ccc");
}
}
[결과값]
[before]{
aaa
}[after]
[before]{
bbb
}[after]
[before]{
ccc
}[after]
MyAdvice의 invoke()
를 통해서 이 메서드들이 호출되었기 때문에 위아래 before, after가 다 붙었다.
추가할 코드를 미리 작성해 놓고도 그 코드가 마치 추가된 것처럼 할려고 하는 것이 AOP
✔️ MyAdvice()에 패턴 추가
package kr.ac.jipark09.aop;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AopMain {
public static void main(String[] args) throws Exception {
MyAdvice myAdvice = new MyAdvice();
// MyClass의 객체를 생성해서 MyAdvice의 invoke메서드에 넘겨줄 것이다.
Class myClass = Class.forName("kr.ac.jipark09.aop.MyClass");
Object obj = myClass.newInstance();
for (Method m : myClass.getDeclaredMethods()) {
myAdvice.invoke(m, obj, null);
}
}
}
// 공통 코드를 메서드로 만들고 각각 호
class MyAdvice {
Pattern pattern = Pattern.compile("a.*"); // 정규식을 사용하여 앞글자가 a인 메서드만 추가
// matcher 추가
boolean matches(Method m) {
Matcher matcher = pattern.matcher(m.getName());
return matcher.matches();
}
void invoke(Method m, Object obj, Object ... args) throws Exception {
// 패턴에 일치하는 경우에만 이 메서드가 발동
if(matches(m)) {
System.out.println("[before]{");
}
m.invoke(obj, args); // aaa(), bbb(). ccc()
if(matches(m)) {
System.out.println("}[after]");
}
}
}
class MyClass {
void aaa() {
System.out.println("aaa");
}
void bbb() {
System.out.println("bbb");
}
void ccc() {
System.out.println("ccc");
}
}
[결과값]
[before]{
aaa
}[after]
bbb
ccc
package kr.ac.jipark09.aop;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AopMain {
public static void main(String[] args) throws Exception {
MyAdvice myAdvice = new MyAdvice();
// MyClass의 객체를 생성해서 MyAdvice의 invoke메서드에 넘겨줄 것이다.
Class myClass = Class.forName("kr.ac.jipark09.aop.MyClass");
Object obj = myClass.newInstance();
for (Method m : myClass.getDeclaredMethods()) {
myAdvice.invoke(m, obj, null);
}
}
}
// 공통 코드를 메서드로 만들고 각각 호출
class MyAdvice {
void invoke(Method m, Object obj, Object ... args) throws Exception {
// @Transactional이 붙은 메서드만 발동
if(m.getAnnotation(Transactional.class) != null ) {
System.out.println("[before]{");
}
m.invoke(obj, args); // aaa(), bbb(). ccc()
if(m.getAnnotation(Transactional.class) != null) {
System.out.println("}[after]");
}
}
}
class MyClass {
@Transactional
void aaa() {
System.out.println("aaa");
}
void bbb() {
System.out.println("bbb");
}
void ccc() {
System.out.println("ccc");
}
}
[결과값]
[before]{
aaa
}[after]
bbb
ccc
❓
deleteUser()
라는 메서드가 있을 때, 코드를 자동으로 추가한다면 어디다가 넣을 수 있을까?
✔️ Advice의 설정은 XML과 어노테이션, 두 가지 방법으로 가능
after throwing은 catch블럭에 해당함
after throwing은 try 끝에 들어가는 문
✔️ advice가 추가될 메서드를 지정하기 위한 패턴
@Around = @Before + @After
메서드에 반환 타입이 있는경우 Object로 반환해야 한다. void는 void로 해도 됨
ProceedingJoinPoint: 메서드의 모든 정보를 가지고 있음
: pjp.getSignature().getName()
➡️ 메서드 이름
: pjp.getArgs()
➡️ 매개변수로 넘기는 값들을 불러옴
메서드 호출결과를 반환하는 이유는 여러개가 적용될 수 있다. 그러면 다음 advice에게 호출 정보를 넘겨줘야 한다. 그래야 after에서 쓸 수 있다.
➡️ @Order
를 써서 호출 정보를 넘겨줄 advice의 순서를 정할 수도 있다.
✔️ AOP를 사용할려면 3가지의 라이브러리가 필요하다.
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
<scope>runtime</scope>
</dependency>
✔️ target으로 사용할 클래스를 만듬
package kr.ac.jipark09.aop;
import org.springframework.stereotype.Component;
@Component
public class MyMath {
public int add(int a, int b) {
int result = a + b;
return result;
}
public int add(int a, int b, int c) {
int result = a + b + c;
return result;
}
public int subtract(int a, int b) {
int result = a - b;
return result;
}
public int mutiply(int a, int b) {
int result = a * b;
return result;
}
}
✔️ root-context_aop.xml 만들고 <aop:aspectj-autoproxy/>를 써준다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<aop:aspectj-autoproxy/>
<context:component-scan base-package="kr.ac.jipark09.aop" />
</beans>
✔️ 실행
package kr.ac.jipark09.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class AopMain2 {
public static void main(String[] args) {
ApplicationContext ac = new GenericXmlApplicationContext("file:src/main/webapp/WEB-INF/spring/**/root-context_aop.xml");
MyMath myMath = (MyMath) ac.getBean("myMath");
int add = myMath.add(3, 5);
int mutiply = myMath.mutiply(3, 5);
System.out.println(add + " " + mutiply);
}
}
✔️ 부가기능이 담긴 클래스를 하나 더 만든다.
package kr.ac.jipark09.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// 부가기능이 담긴 메서드를 만든다.
@Component
@Aspect
public class LoggingAdvice {
// pointcut: 부가기능이 적용될 메서드의 패턴
@Around("execution(* kr.ac.jipark09.aop.*.MyMath(..))")
public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("<<[start]" + pjp.getSignature().getName() + Arrays.toString(pjp.getArgs()));
// target의 메서드가 호출돼서 넘어온다.
Object result = pjp.proceed();
System.out.println("result=" + result);
System.out.println("[end]>>" + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
[결과값]
<<[start] add[3, 5]
result=8
[end]>>12ms
<<[start]mutiply[3, 5]
result=15
[end]>>0ms
8 15
Reference
: https://engkimbs.tistory.com/746
: https://fastcampus.co.kr/dev_academy_nks