@Aspect 프록시

이원석·2024년 3월 4일

Spring

목록 보기
20/20
post-thumbnail
*인프런 김영한 강사님의 강좌를 참고하여 정리한 내용입니다.*

이전까지는 프록시를 적용하기 위해 Pointcut, Advice 로 이루어진 Advisor를 만들어 스프링 빈으로 등록해야 했다.

@Bean
public Advisor advisor(LogTrace logTrace) {
	NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
	pointcut.setMappedNames("request*", "order*", "save*");
	LogTraceAdvice advice = new LogTraceAdvice(logTrace);

	return new DefaultPointcutAdvisor(pointcut, advice);
}

스프링의 자동 프록시 생성기의 경우 빈으로 등록된 Adivosr들을 찾고, 적용될 스프링 빈들에 자동으로 프록시를 적용했다.

이보다 더 간단한 방법으로 프록시 생성이 가능한데, AspectJ에서 제공하는 @Aspect 어노테이션을 사용하면 된다!




@Aspect

복습할겸 이전의 빈 후처리기를 통한 프록시 생성을 확인해보자.

1. Custom 빈 후처리기를 사용한 프록시 생성

public class PackageLogTraceProxyPostProcessor implements BeanPostProcessor {
 	
    private final String basePackage;
    private final Advisort advisor
    
    public PackageLogTraceProxyPostProcessor(String basePackage, Advisor advisor) {
		this.basePackage = basePackage;
        this.advisor = advisor;
    }
    
    @Override
	public Object postProcessAfterInitialization(Object bean) throws BeansException {
    
		// 프록시 적용 대상 검증로직
		// 빈 객체가 포함된 패키지명
		String packageName = bean.getClass().getPackageName();

		// 해당 빈의 패키지 명이 프록시를 생성하고자 하는 패키지명 basePackage에 포함되지 않다면
		if (!packageName.startWith(basePackage) {
			// 원본 빈 객체를 반환
			return bean;
		}

		// 프록시 대상이라면 프록시 반환
		ProxyFactory proxyFactory = new ProxyFactory(bean);
		proxyFactory.addAdvisor(advisor);
		Object proxy = proxyFactory.getProxy();
		
        return proxy;
	}
}

ProxyFactory를 통해 프록시를 생성하며 부가 기능인 Adivosr를 추가하여 Proxy를 반환했다. 해당 빈 후처리기를 Config.class에 빈으로 등록하면 객체를 후킹하여 Proxy를 생성한다.



2. 스프링의 빈 후처리기를 사용한 프록시 생성

@Bean
public Advisor advisor1(LogTrace logTrace) {
	NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
	pointcut.setMappedNames("request*", "order*", "save*");
	LogTraceAdvice advice = new LogTraceAdvice(logTrace);

	return new DefaultPointcutAdvisor(pointcut, advice);
}

@Bean
public Advisor advisor2(LogTrace logTrace) {
	AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
	pointcut.setExpression("execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))");
	LogTraceAdvice advice = new LogTraceAdvice(logTrace);
	
	return new DefaultPointcutAdvisor(pointcut, advice);
 }

AdvisorProxyFacotry에 추가할 필요없이 빈으로만 등록해도 Pointcut을 참조하여 알아서 프록시를 생성한다.



3. @Aspect 어노테이션을 사용한 프록시 생성

@Aspect
public class LogTraceAspect {
	// 로그 추적기
	private final LogTrace logTrace;

	// 생성자 주입
    ...

	// AspectJ의 정규 표현식으로 Pointcut과 유사
	@Around("execution(* hello.proxy.app..*(..))")
	public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
		// 부가 기능(로그 추적기) 로직..
		
        // 실제 호출 대상 (target)을 호출.
        Object result = joinPoint.proceed();
        
		// 부가 기능(로그 추적기) 로직..
        
        return result;
	}
}

1,2번과 어떤점이 다를까?

우선 Advisor를 찾아볼 수 없다! Advisor를 사용하지 않는것인가? 그렇지 않다.

execute() 메서드에 @Around 어노테이션을 적용했는데, 이것이 바로 AspectJ의 정규표현식을 사용한 Pointcut이다. 내부 메서드의 로직들은 부가 기능 로직인 Advice이다.

execute() 메서드의 인자 joinPointAdviceexecute()에서 MethodInovation invocation을 인자로 받는것과 유사한 기능을 한다. (내부에 실제 호출 대상 traget, 전달 인자, 메타 데이터/어떤 객체와 메서드가 호출되었는가)

3-1. 개선점

PointcutAdvice를 통해 직접 Advisor를 생성하지 않고, AspectJ의 어노테이션들을 통해 간단하게 Advisor를 생성할 수 있다.

그런데 누가? 어떻게? Advisor를 생성하는거지?




프록시의 생성 과정

자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)는 빈으로 등록된 Advisor를 찾아와 Pointcut을 참고하여 필요한 곳에 프록시를 생성하고 적용해주었다.

추가적인 기능으로 1) 빈으로 등록된 객체중 @Aspect를 찾아 Adivosr로 만들어준다. 그리고 이를 기반으로 2) 프록시 객체를 생성 및 후킹을 통해 빈 저장소에 등록한다. AnnotationAwareAspect가 이름앞에 붙어있는 이유이기도 하다.



1. 빈으로 등록된 객체중 @Aspect를 찾아 Adivosr로 만드는 과정

1. 실행:
스프링 로딩 시점에 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator) 호출.
2. 모든 @Aspect 빈 조회:
스프링 컨테이너에서 @Aspect 어노테이션이 붙은 스프링 빈을 모두 조회한다
3. Advisor 생성:
@Aspect 어드바이저 빌더를 통해 Advisor를 생성한다.
4. @Aspect 기반 어드바이저 저장:
생성한 어드바이저를 @Aspect 어드바이저 빌더 내부에 저장한다.



2. 프록시 객체를 생성 및 후킹을 통해 빈 저장소에 등록하는 과정

1. 생성:
스프링 빈 대상이 되는 객체를 생성한다. (@Bean, 컴포넌트 스캔 모두 포함)
2. 전달:
빈 객체를 빈 후처리기에 전달한다.
3-1. Advisor 빈 조회:
스프링 컨테이너에서 빈으로 등록된 Advisor를 모두 조회한다.
3-2. @Aspect Advisor 조회
@Aspect Adivosr Builder 내부에 저장된 Advisor를 모두 조회한다.
4. 프록시 적용 대상 검증:
3-1, 3-2에서 조회한 Advisor들의 Pointcut을 통해 프록시 적용 대상 객체들을 구분한다. (ClassName, MethodName) 이 때 하나의 조건이라도 만족하면 프록시 적용 대상이다.
5. 프록시 생성
프록시 적용 대상이면 프록시를 생성하고 반환, 아닐경우 원본 객체를 반환한다.
6. 빈 등록
반환된(프록시 or 원본) 객체를 스프링 빈으로 등록한다.


이렇게 스프링의 AOP 프레임워크의 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)는 @Aspect 객체를 Advisor로 등록도 해주고, 빈으로 등록한 Advisor도 조회하여 빈 후처리기로서의 역활을 한다.




그래서..

지금까지 핵심기능과 부가기능의 분리를 위해 다양한 디자인 패턴과 프록시의 생성/필터링 및 자동화를 알아보았다. 결국 어떻게 보면 애플리케이션 전반에 부가기능을 적용하기 위해 이렇게 많은 과정이 있었던 것이다...

이렇게 애플리케이션의 가로지르는 기능을 횡단 관심사(corss-cuting concerns)라고 한다. 횡단 관심사를 해결하기 위해 프록시의 개념부터 활용 자동화 까지 알아보았는데, 이를 전문으로 해결하는 것이 바로 스프링 AOP 이다!





참고문헌
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

0개의 댓글