class MyClass {
void aaa() {
System.out.println("[before](");
System.out.println("aaa() is called.");
System.out.println(")[after]");
void aaa2() {
System.out.println("[before]{");
System.out.println("aaa2() is called.");
System.out.println(")[after]");
void bbb() {
System.out.println("[before]{");
System.out.println("bbb() is called.");
System.out.println("}[after]");
}
}
위 코드에서 핵심기능은 aaa,aaa2,bbb를 호출하는 기능이고 위아래로 before, after는 부가기능이며 중복된다.이를 reflection api와 aop 개념을 적용하여 좀 더 변경이 유리한 코드로 바꾸어보자.
package com.fastcampus.ch3.aop;
import java.lang.reflect.Method;
public class AopMain {
public static void main(String[ ] args) throws Exception {
MyAdvice myAdvice = new MyAdvice();
Class myClass = Class.forName("com.fastcampus.ch3.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);
System.out.println(")[after]");
}
}
class MyClass {
void aaa() {
System.out.println("aaa() is called.");
}
void aaa2() {
System.out.println("aaa2() is called.");
}
void bbb() {
System.out.println("bbb() is called.");
}
}
위 코드를 실행하면
[before](
bbb() is called.
)[after]
[before](
aaa() is called.
)[after]
[before](
aaa2() is called.
)[after]
다음과 같다. 위 코드는 reflection api를 사용해서 MyClass의 정보를 얻어온 후, invoke 메서드를 실행하여 중간에 코드를 삽입한 것이다.
위 코드에서 a로 시작하는 메서드만 실행하고 싶다면 Pattern을 사용해서 실행할 수 있다.
package com.fastcampus.ch3.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();
Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
Object obj = myClass.newInstance();
for(Method m : myClass.getDeclaredMethods()) {
myAdvice.invoke(m, obj, null);
}
}
}
class MyAdvice {
Pattern p = Pattern.compile("a.*");
boolean matches(Method m) {
Matcher matcher = p.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);
if(matches(m))
System.out.println(")[after]");
}
}
class MyClass {
void aaa() {
System.out.println("aaa() is called.");
}
void aaa2() {
System.out.println("aaa2() is called.");
}
void bbb() {
System.out.println("bbb() is called.");
}
}
pattern을 a로 시작하는 패턴으로 주고 matches 메서드를 사용해서 실행하면
[before](
aaa() is called.
)[after]
bbb() is called.
[before](
aaa2() is called.
)[after]
위와 같이 b로 시작하는 메서드는 부가기능이 붙지 않는 것을 볼 수 있다.
위를 통해 알수 있는 AOP의 기능은 부가기능(advice)을 동적으로 추가해 주는 기술이라는 것을 알 수 있다. 메서드의 시작 또는 끝에 자동으로 코드를 추가해준다.
target : advice가 추가될 객체
advice : target에 동적으로 추가될 부가 기능(코드)
join point : advice가 추가(join)될 대상(메서드)
pointcut : join point들을 정의한 패턴
proxy : target에 advice가 동적으로 추가되어 생성된 객체
weaving : target에 advice를 추가해서 proxy를 생성하는 것
종류 애너테이션 설명
around advice @Around 메서드의 시작과 끝 부분에 추가되는 부가 기능
before advice @Before 메서드의 시작부분에 추가되는 부가 기능
after advice @After 메서드의 끝 부분에 추가되는 부가 기능
after returning @AfterReturning 예외가 발생하지 않았을 때, 실행되는 부가기능
after throwing @AfterThrowing 예외가 발생했을 때, 실행되는 부가기능
package com.fastcampus.ch3.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 {
@Around("execution(* com.fastcampus.ch3.aop.MyMath.add*(..))") //pointcut - 부가기능이 적용될 메서드의 패턴
public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("<<[start] " + pjp.getSignature().getName()+ Arrays.toString(pjp.getArgs()));
Object result = pjp.proceed(); //target의 메서드를 호출
System.out.println("result=" + result);
System.out.println("[end]>> " + (System.currentTimeMillis() - start)+"ms");
return result;
}
}
package com.fastcampus.ch3.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 multiply(int a, int b) {
int result = a * b;
return result;
}
}
package com.fastcampus.ch3.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 mn = (MyMath)ac.getBean("myMath");
mn.add(3,5);
mn.add(1,2,3);
mn.multiply(3,5);
}
}
<<[start] add[3, 5]
result=8
[end]>> 31ms
<<[start] add[1, 2, 3]
result=6
[end]>> 0ms
위와 같이 작성하면 @Around로 메서드의 위아래를 부가 기능으로 감싸고 @Around에 패턴을 다음과 같이 적용하면 a로 시작하는 메서드만 적용된다. AopMain2 클래스에서 multiply 메서드는 적용 제외되어서 맨 아래와 같은 출력결과가 나오게 된다.