두원공대88학번뚜뚜 2021. 1. 20. 17:01
package main;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import chap07.Calculator;
import config.AppCtx;

public class MainAspect {
public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = 
			new AnnotationConfigApplicationContext(AppCtx.class);
	Calculator cal = ctx.getBean("calculator", Calculator.class);
	long fiveFact= cal.factorial(5);
	System.out.println("cal.factorial(5) = " + fiveFact);
	System.out.println(cal.getClass().getName());
	ctx.close();
}
}
package aspect;

import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect // 공통기능을 제공하는 Aspect 구현클래스의 적용처
public class ExeTimeAspect {
	@Pointcut("execution(public * chap07..*(..))") //Aspect 애노테이션 적용클래스는 advice와 pointcut 제공
	//pointcut은 공통기능 적용할 대상을 설정. chap07패키지와 그 하위 패키지의 public 메서드를 pointcut으로 설정
		private void publicTarget() {
			
		}
	
	@Around("publicTarget()") 
	//publicTarget()메서드에 정의한 Pointcut에 공통기능을 적용한다는 뜻. 
	//publicTarget()은 chap07패키지와 그 하위패키지에 위치한 public을 Pointcut으로 설정했으므로,
	//chap07과 그 하위패키지에 속한 빈객체의 public메서드에 @Around가 붙은 measure()메서드를 적용
	
	//measure()메서드의 ProceedingJoinPoint타입 인자는 프록시 대상 객체의 메서드 호출시 사용
	//Object result=joinPoint.preceed()처럼, preceed를 이용해 실제 대상 객체의 메서드를 호출, 실행
	//따라서 시간측정은 그 전(long start)과 후(finally long finish)에 이뤄짐
	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
		long start = System.nanoTime();
		try {
			Object result = joinPoint.proceed();
			return result;
		}
		finally {
			long finish = System.nanoTime();
			Signature sig= joinPoint.getSignature();
			System.out.printf("%s.%s(%s) 실행시간 : %d ns\n",
					joinPoint.getTarget().getClass().getSimpleName(),
					sig.getName(), Arrays.toString(joinPoint.getArgs()),
					(finish-start));
		}
	}
}
//getSignature(), getTarget(), getArgs()는 호출한 메서드의 시그니처, 대상객체, 인자목록 구성에 쓰임. 
//대상객체 클래스 이름과 메서드 이름을 출력.
//이렇게 공통기능 적용에 필요한 코드 구현 완료

결과 첫번째줄은 ExeTimeAspect클래스의 measure()메서드가 출력

세번째는 mainaspect가 출력한 것

출력결과를 보면 calculator타입이 recCalculator클래스가 아닌 $Proxy17로, 이는 스프링이 생성한 프록시타입

 

메인에서 컨테이너 생성 후, 컨테이너에서 calculator란 이름을 가진 빈객체를 가져와 cal에 삽입

이 cal에 5 집어넣은 결과가 fiveFact임

따라서 calculator cal의 factorial이 실행됨

이 때 중요한 것은, Calculator cal = ctx.getBean.. 에서 구한 타입은 RecCalculator 클래스가 아니라, $Proxy17이다.

실제로, AOP를 적용하지 않으면(=AppCtx클래스에서 exeTimeAspect()메서드를 주석처리하면), 

chap07.RecCalculator가 출력되는 것을 알 수 있으며, 이는 실행타입이 RecCalculator임을 알 수 있다.

 

여기서 구현하고자 한 건 AroundAdvice로, 대상 객체의 메서드 실행 전, 후, 익셉션 발생 시점에 공통 기능을 실행하는 것. 즉, cal(=대상 객체)의 메서드(fiveFact) 실행 전후에 Aspect를 통해 여러 객체에 적용될 공통기능을 클래스로 정의하고, 공통기능을 적용할 대상을 설정(pointcut)하고, Around("..")를 통해 ".."메서드에 정의한 Pointcut에 공통기능을 적용한다.

 

따라서, AppCtx의 configuration과 bean을 통해 이 내부의 빈 객체가 형성

-->@EnableAspectJAutoProxy을 설정클래스에 붙임으로 인해, 스프링은 @Aspect가 붙은 빈객체를 찾아 빈객체의 @Pointcut설정과 @Around설정을 사용

--> cal의 fiveFact실행이 되면, cal은 공통기능 적용대상 내부에 있으므로 공통기능이 실행됨(publicTarget()이)

--> chap07과 그 하위패키지에 속한 빈객체(여기선 calculator)의 public메서드(즉, return new RecCalculator()가 있는 public)에 @Around가 붙은 measure()메서드를 적용

--> 여기에서, measure()메서드의 ProceedingJoinPoint타입 인자는 프록시 대상 객체의 메서드 호출시 사용.
--> Object result=joinPoint.preceed()에서, joinPoint는 public Object measure(ProceedingJoinPoint joinPoint)의 인자로, 따라서 measure에 들어온 건 public메서드, 즉 return new RecCaculator의 RecCalculator 이름이다. 따라서 preceed는 RecCalculator, 즉 실제 대상 객체(=RecCalculator)의 메서드를 호출, 실행한다는 뜻.

 

이후, mainaspect->$proxy17->exetimeaspect->proceedingjoinpoint->reccalculator 순이었으니, 다시 역순으로 계속 return.

 

이 때, 계층적으로는 interface인 calculator을 $Proxy17과 RecCalculator가 상속받으므로, MainAspect를 Calculator cal= ctx.getBean("calculator", Calculator.class)에서 양옆을 RecCalculator로 변경 시, AOP를 위한 프록시 객체를 생성시, 실제 생성할 빈 객체가 인터페이스를 상속하면 인터페이스를 이용해 프록시를 생성하므로, RecCalculator 클래스는 Calculator인터페이스를 상속하므로, 이를 상속받은 프록시 객체를 생성한다. 따라서, 빈의 실제 타입이 Rec이여도, calculator이름에 해당하는 빈 객체 타입은, Calculaor 인터페이스를 상속받은 프록시가 된다.

 

따라서, 인터페이스가 아닌 클래스를 이용해 프록시를 상속받으려면 방식을 바꿔야 한다.

@EnableAspectJAutoProxy(proxyTargetClass= true)

이러면, 자바클래스를 상속받아 클래스를 생성. getBean() 메서드의 실제 클래스를 이용해 빈객체 구하기가 가능.

RecCalculator cal= ctx.getBean("calculator", RecCalculator.class)