FireDrago
[Spring] 빈 후처리기 , @Aspect 본문
빈 후처리기

빈 후처리기를(BeanPostProcessor) 사용하면, 빈 저장소에 등록할 목적으로 생성한 객체를 등록전에 조작할 수 있다.
객체를 조작할 수도 있고, 완전히 다른 객체로 바꿔치기 하는 것도 가능하다.
빈 후처리기를 사용하면 개발자가 등록하는 모든 빈을 중간에 조작할 수 있다. 이 말은 빈 객체를 프록시로 교체할수 있다.
public interface BeanPostProcessor {
/**
* 객체 생성 이후에 초기화 발생 전에 호출된다.
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 객체 생성 이후에 초기화 발생 후에 호출된다.
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
빈 후처리기를 사용하려면 BeanPostProcessor 인터페이스를 구현하고, 스프링 빈으로 등록하면 된다.
postProcessBeforeInitialization : 객체 생성 이후에 @PostConstruct 같은 초기화가 발생하기
전에 호출되는 포스트 프로세서이다.
postProcessAfterInitialization : 객체 생성 이후에 @PostConstruct 같은 초기화가 발생한 다음
에 호출되는 포스트 프로세서이다
빈 후처리기 - 프록시 등록

@Slf4j
public class PackageLogTracProxyPostProcessor implements BeanPostProcessor {
private final String basePackage;
private final Advisor advisor;
public PackageLogTracProxyPostProcessor(String basePackage, Advisor advisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// 프록시 적용 대상 여부 체크
// 프록시 적용 대상 아니면 원본을 그대로 반환
String packageName = bean.getClass().getPackageName();
if (!packageName.startsWith(basePackage)) {
return bean;
}
// 프록시 대상이면 프록시 만들어서 반환
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(advisor);
Object proxy = proxyFactory.getProxy();
log.info("create proxy: target={} proxy={}", bean.getClass(), proxy.getClass());
return proxy;
}
}
PackageLogTraceProxyPostProcessor 는 원본 객체를 프록시 객체로 변환하는 역할을 한다.
이때 프록시 팩토리를 사용하는데, 프록시 팩토리는 advisor 가 필요하기 때문에 이 부분은 외부에서 주입 받도록 했다.
패키지 네임을 활용하여, 특정 패키지 경로의 빈에만 프록시를 적용하도록 했다.
다른 빈들은 원본 빈을 그대로 반환한다. 해당 빈은 프록시를 생성하여 프록시가 빈으로 등록될 수 있도록 했다.
@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class BeanPostProcessorConfig {
@Bean
public PackageLogTracProxyPostProcessor logTracProxyPostProcessor(LogTrace logTrace) {
return new PackageLogTracProxyPostProcessor("hello.proxy.app", getAdvisor(logTrace));
}
private Advisor getAdvisor(LogTrace logTrace) {
// 포인트 컷 생성
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
// 어드바이스 생성
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
// 어드바이저 = 포인트컷 + 어드바이스
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
@Bean logTraceProxyPostProcessor() : 특정 패키지를 기준으로 프록시를 생성하는
빈 후처리기를 스프링 빈으로 등록한다. 빈 후처리기는 스프링 빈으로만 등록하면 자동으로 동작한다.
여기에 프록시를 적용할 패키지 정보( hello.proxy.app )와 어드바이저( getAdvisor(logTrace) )를 넘겨준다
@Import(BeanPostProcessorConfig.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
@Bean
public LogTrace logTrace() {
return new ThreadLocalLogTrace();
}
}
@Import(BeanPostProcessorConfig.class) : 설정파일을 등록한다.
빈 후처리기 프록시 등록 장점
1. 수동 등록 빈 뿐만아니라, 컴포넌트 스캔을 사용한 빈도 등록될때 자동으로 프록시가 등록된다.
2. 프록시 객체를 일일히 빈으로 만들어서 등록할 필요가 없다.
스프링이 제공하는 빈 후처리기
빈 후처리기를 직접 만들어 등록할때, 특정 패키지 경로를 기준으로 대상 빈을 체크했다.
그런데 어드바이저의 포인트컷은 이미 클래스, 메서드 단위의 필터기능을 가지고 있다.
이를 빈 후처리기에서도 사용할 수 있다. 즉 포인트컷은 두가지 사용기능이 있는것이다.
포인트컷의 두가지 기능
1. 프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다. (빈 후처리기 - 자동 프록시 생성)
2. 프록시가 생성되고, 어떤 메서드가 호출 되었을때, 어드바이스를 적용할 지 판단한다. (프록시 내부)

스프링 부트는 AnnotationAwareAspectJAutoProxyCreator 라는 빈 후처리기를 자동으로 등록해준다.
이 빈 후처리기는 스프링 빈으로 등록된 Advisor 들을 자동으로 찾아서 프록시가 필요한곳에 프록시를 적용해준다.
Advisor 에는 이미 pointcut이 포함되어 있기때문에 어떤 스프링 빈에 프록시를 적용해야 할지 알 수 있다.
또한 @Aspect 도 자동으로 인식해서 프록시를 만들고 AOP를 적용해준다.

스프링 부트 빈 후처리기 ( AnnotationAwareAspectJAutoProxyCreator ) 기능
1. 어드바이저를 찾아 프록시 자동 적용
2. @Aspect 찾아 프록시 만들고, AOP 적
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {
@Bean
public Advisor advisor3(LogTrace logTrace) {
// 포인트컷 설정
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// AspectJ 표현식을 통해 포인트컷을 설정한다.
pointcut.setExpression("execution(* hello.proxy.app..*(..)) && " +
"!execution(* hello.proxy.app..noLog(..))");
// 어드바이스 설정
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
// 어드바이저 빈 설정
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
설정파일을 통해 어드바이저를 빈으로 등록했다. 이렇게만 해도 이제 프록시가 자동으로 생성되고 실행된다.
execution(* hello.proxy.app..*(..)) : AspectJ가 제공하는 포인트컷 표현식이다.
* : 모든 반환 타입
hello.proxy.app.. : 해당 패키지와 그 하위 패키지
*(..) : * 모든 메서드 이름, (..) 파라미터는 상관 없음
@Import(AutoProxyConfig)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
@Bean
public LogTrace logTrace() {
return new ThreadLocalLogTrace();
}
}
실행하면, 어드바이스의 부가기능이 no-log 메서드를 제외한 다른 메서드를 호출할때, 작동하는 것을 확인 할 수 있다.
하나의 프록시, 여러 Advisor 적용
프록시 자동 생성기는 프록시를 하나만 생성한다.
여러 어드바이저의 조건을 만족한다고 해도 프록시 하나에 여러 어드바이저를 포함시키는 방식을 사용한다.
@Aspect 애노테이션으로 어드바이저 만들기
프록시를 적용하려면 포인트컷과 어드바이스로 구성되어 있는 어드바이저( Advisor )를 스프링 빈으로 등록하면 된다.
그러면 나머지는 앞서 배운 자동 프록시 생성기가 모두 자동으로 처리해준다.
스프링은 @Aspect 애노테이션으로 매우 편리하게 포인트컷과 어드바이스로 구성되어 있는 어드바이저 생성 할수있다.

@Slf4j
@Aspect
public class LogTraceAspect {
private final LogTrace logTrace;
public LogTraceAspect(LogTrace logTrace) {
this.logTrace = logTrace;
}
// @Around 사용하여 포인트컷 설정
@Around("execution(* hello.proxy.app..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
TraceStatus status = null;
try {
String message = joinPoint.getSignature().toShortString();
status = logTrace.begin(message);
// 로직 호출
Object result = joinPoint.proceed();
logTrace.end(status);
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
@Aspect : 애노테이션 기반 프록시를 적용할 때 필요하다.
@Around("execution(* hello.proxy.app..*(..))") :@Around 의 값에 포인트컷 표현식을 넣는다.
표현식은 AspectJ 표현식을 사용한다. @Around 의 메서드는 어드바이스( Advice )가 된다.
ProceedingJoinPoint joinPoint : 어드바이스에서 살펴본 MethodInvocation invocation 과 유사한 기능이다.
내부에 실제 호출 대상, 전달 인자, 그리고 어떤 객체와 어떤 메서드가 호출되었는지 정보가 포함되어 있다.
joinPoint.proceed() : 실제 호출 대상( target )을 호출한다.
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AopConfig {
@Bean
public LogTraceAspect logTraceAspect(LogTrace logTrace) {
return new LogTraceAspect(logTrace);
}
}
@Import({AppV1Config.class, AppV2Config.class}) : V1, V2 애플리케이션은 수동으로 스프링빈으로 등록해야 동작한다.
@Bean logTraceAspect() : @Aspect 가 있어도 스프링 빈으로 등록을 해줘야 한다.
LogTraceAspect 에 @Component 애노테이션을 붙여서 컴포넌트 스캔을 사용해서 스프링 빈으로 등록해도된다.
자동 프록시 생성기의 두가지 역할
자동 프록시 생성기( AnnotationAwareAspectJAutoProxyCreator )는
1. Advisor 를 자동으로 찾아와서 필요한 곳에 프록시를 생성하고 적용해준다고 했다.
2. @Aspect 를 찾아서 이것을 Advisor 로 만들어준다.
쉽게 이야기해서 지금까지 학습한 기능에 더해 @Aspect 를 Advisor 로 변환해서 저장하는 기능도 한다.
그래서 이름 앞에 AnnotationAware (애노테이션을 인식하는)가 붙어 있는 것이다.

지금까지 프록시를 사용해서 이러한 횡단 관심사를 어떻게 해결하는지 점진적으로 매우 깊이있게 학습하고 기반을 다져
두었다. 이제 이 기반을 바탕으로 이러한 횡단 관심사를 전문으로 해결하는 스프링 AOP에 대해 본격적으로 알아보자
