이전에는 ProxyFactory를 통해서 프록시 객체를 생성하여 수동 Bean 등록하여 로직 분리를 진행 하였다.
이전 글
스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장에 등록하기 직전에 조작할 때 사용 할 수 있는 Processor
- 즉,
TestRepository
라는 Interface를 구현한 객체인TestRepositoryImpl
을@Repository
Annotation 으로 자동 등록을 할 때, 실제 등록되는 객체를ProxyTestRepository
객체를 등록할 수 있는 것입니다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
AnnotationAwareAspectJAutoProxyCreator
라는 빈 후처리기가 자동 등록됩니다.Advisor
들을 찾아서 자동으로 적용 해줍니다.Advisor
안에는 Pointcut
과 Advice
가 있으므로, Advisor
만으로도 어떤 객체에 프록시를 등록하고 어떤 동작을 해야될 지 알 수 있습니다.생성
: 스프링이 스프링 빈 대상이 되는 객체를 생성합니다. @Bean, @Controller 등등전달
: 생성된 객체를 빈 저장소에 등록하기 직전에, 빈 후처리기에 전달합니다.조회
: 모든 Advisor
를 조회 합니다.대상 체크
: 조회한 Advisor
에 포함된 Pointcut
을 사용해서 해당 객체가 프록시를 적용할 대상이 아닌지 확인힙니다.적용
: 프록시 적용 대상이라면, 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록 합니다. 프록시 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록합니다.예제에 사용한 코드 : Github
- baekgwa.proxypattern.web.componentscan 참조
TestController
를 프록시 객체로 후처리 해보도록 하겠습니다.@Configuration
@RequiredArgsConstructor
public class AutoProxyConfig {
private final Logger logger;
@Bean
public Advisor loggerAdvisor() {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("save", "saveName");
LoggerAdvice advice = new LoggerAdvice(logger);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
@Import({AutoProxyConfig.class, ProxyFactoryConfig.class})
public class ProxyPatternApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyPatternApplication.class, args);
}
}
NameMatchMethodPointcut
을 등록하였는데, 이를 조건으로 프록시를 등록합니다.TestControllerV2
처럼, healthCheck()
메서드는 포인트컷
이 적용되지 않는 범위라면, 생성은 하되 사용할때는 사용되지 않습니다.package baekgwa.proxypattern.gloabl.config.v3_autoproxy;
import baekgwa.proxypattern.gloabl.config.advice.LoggerAdvice;
import baekgwa.proxypattern.gloabl.logger.Logger;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class AutoProxyConfigV2 {
private final Logger logger;
@Bean
public Advisor loggerAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"execution(* baekgwa.proxypattern.web.componentscan..*(..)) && "
+ "!execution(* baekgwa.proxypattern.web.componentscan..healthCheck(..))");
LoggerAdvice advice = new LoggerAdvice(logger);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
componentscan
하위 패키지에 모든 메서드 (모든 파라미터)가 만족되고, healthCheck 메서드는 아닌 것 이라는 조건 입니다.예제에 사용한 코드 : Github
- baekgwa/proxypattern/gloabl/config/advice/LoggerAdviceAspect 참조
@Aspect
@RequiredArgsConstructor
public class LoggerAdviceAspect {
private final Logger logger;
@Around("execution(* baekgwa.proxypattern.web.componentscan..*(..)) && "
+ "!execution(* baekgwa.proxypattern.web.componentscan..healthCheck(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
String message = joinPoint.getSignature().toShortString();
LogInfo logInfo = logger.start(message);
Object result = joinPoint.proceed();
logger.end(logInfo);
return result;
}
}
@SpringBootApplication(scanBasePackages = "baekgwa.proxypattern.web")
@Import(ProxyFactoryConfig.class)
public class ProxyPatternApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyPatternApplication.class, args);
}
@Bean
public Logger logger(){
return new LoggerImpl();
}
@Bean
public LoggerAdviceAspect loggerAdviceAspect(){
return new LoggerAdviceAspect(logger());
}
}
.getSignature()
를 통해 가져온 값은 다음과 같이 우리가 표현한 식과 비슷하게 나왔습니다.@Repository
, @Controller
등, @Componenet
를 통해 사용할 객체를 등록 하였습니다. 하지만 실제로 실행되는 객체는 프록시 객체가 실행되었습니다.@Aspect
를 통해 Advisor를 생성 할 수 있었습니다.Spring AOP
에 대해서 조금더 학습하고 AspectJ
표현식의 다양한 방법에 대해서 더 알아보도록 하겠습니다.