실제 주 업무를 가진 클래스를 호출하게 된다.
사용자는 proxy를 호출하게 되고 사용하고 싶은 코드를 순서대로 삽입하면 된다.
굳이 본 코드에 Cross-Cutting Concern을 삽입하지 않아도 이런 방식을 사용하면 마치 삽입한 것처럼 사용이 가능하다.
Proxy를 Cross-Cutting Concern가 결합했다가 분리했다가 해야하기 위해 코드를 변경, 수정해야 하는 문제가 있다.
=> 스프링 DI를 사용해 해결할 수 있다.
public int total() {
long start= System.currentTimeMillis();
int result = kor+eng+math+com; // 주 업무
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end= System.currentTimeMillis();
String message = (end-start) + "ms가 걸렸습니다.";
System.out.println(message);
return result;
}
Cross-Cutting Concern을 따로 분리하기 위해 Proxy 사용
Proxy 사용법
Proxy.newProxyInstance(
클래스.class, 클래스가 참조하는 인터페이스명,
invocationHandler() 인터페이스를 구현하는 클래스)
public int total() {
int result = kor+eng+math+com;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
Exam exam = new NewlecExam(1,1,1,1);
Exam proxy =
(Exam) Proxy.newProxyInstance(NewlecExam.class.getClassLoader(), // 클래스
new Class[] {Exam.class}, // 클래스 참조 인터페이스
new InvocationHandler() { // 핸들러
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Cross-Cutting Concern 삽입을 위한 메소드로 사용할 함수 명과 인자 값이 넘어오게 된다.
long start= System.currentTimeMillis();
Object result = method.invoke(exam, args); // method.invoke(객체명,args) 사용할 주업무
long end= System.currentTimeMillis();
String message = (end-start) +
"ms가 걸렸습니다.";
System.out.println(message);
return result;
}
});
System.out.printf("total is %d\n", exam.total()); // proxy를 사용하지 않은 호출
System.out.printf("total is %d\n", proxy.total()); // proxy를 사용한 호출
}
Cross-Cutting Concern이 필요할 시 proxy를 사용해서 함수를 호출해준다.
프록시 객체의 콜백 기능 중 하나로, 프록시 객체에서 타겟 객체를 호출하기 전, 후로 비지니스 로직을 추가하거나, 타겟 클래스가 아닌 다른 객체의 메소드를 호출하거나, 인자 값을 변경할 수 있다.
구현 방법은 커스텀한 클래스에 MethodInterceptor 인터페이스를 상속받은 후 intercept 메서드를 오버라이드 한다.
기본 자바 코드에서는
Proxy.newProxyInstance(
1. 클래스.class
, 2. 클래스가 참조하는 인터페이스명
,
3. invocationHandler() 인터페이스를 구현하는 클래스
)
newProxyInstance 함수를 통해 Proxy 객체를 만들어서 AOP를 구현하였다.
Spring에서는 1. 클래스와 3.invocationHandler() 구현만 해주면 2.클래스 참조 인터페이스는 스프링이 알아서 해결한다.
public class Program {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring/aop/setting.xml");
Exam proxy = (Exam) context.getBean("proxy"); // proxy 빈 주입
System.out.printf("total is %d\n", proxy.total());
System.out.printf("avg is %f\n", proxy.avg());
}
}
<bean id="target" class="spring.aop.entity.NewlecExam" p:kor="1" p:eng="1" p:math="1" p:com="1"/>
// 주 업무 빈
<bean id="logAroundAdvice" class="spring.aop.advice.LogAroundAdivce"/>
// 보조 업무 수행하는 crosscutting concern 빈
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> // proxy 빈
<property name="target" ref="target"></property> // 주 업무 설정
<property name="interceptorNames"> // 핸들러 역할
<list> // 참조할 목록
<value>logAroundAdvice</value> // crosscutting Corcern
</list>
</property>
</bean>
org.springframework.aop.framework.ProxyFactoryBean 클래스 사용 시 target과 interceptorNames의 이름을 가진 property를 무조건 사용해야한다.
public class LogAroundAdvice implements MethodInterceptor { // AroundAdvice
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start= System.currentTimeMillis(); // 보조 업무
Object result = invocation.proceed(); // 주 업무
long end = System.currentTimeMillis(); // 보조 업무
String message = (end-start) +"ms가 걸렸습니다."; System.out.println(message); // 보조 업무
return result;
}
}
MethodInvocation 타깃 오브젝트의 메소드를 내부적으로 실행
Before - org.springframework.aop.MethodBeforeAdvice
Around - org.springframework.aop.MethodInterceptor
After Returning - org.springframework.aop.AfterReturningAdvice
After Throwing - org.springframework.aop.ThrowsAdvice
proceed() 메소드를 통해 주 업무를 result에 할당해준다.
public class LogBeforeAdvice implements MethodBeforeAdvice{ // Before Advice
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("BeforeAdvice");
}
}
Before Advice는 무조건 주 업무 전에 실행되기 때문에 주 업무를 감쌀 필요가 없다.