일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch14
- 타임리프 - 기본기능
- 스프링 mvc1 - 서블릿
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch2
- 스프링 고급 - 스프링 aop
- 스프링 mvc1 - 스프링 mvc
- 코드로 시작하는 자바 첫걸음
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch13
- 스프링 mvc2 - 타임리프
- 게시글 목록 api
- 2024 정보처리기사 수제비 실기
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch6
- 스프링 입문(무료)
- @Aspect
- 자바의 정석 기초편 ch3
- 스프링 db1 - 스프링과 문제 해결
- jpa 활용2 - api 개발 고급
- 스프링 db2 - 데이터 접근 기술
- Today
- Total
나구리의 개발공부기록
빈 후처리기(소개/예제/적용/정리), 스프링이 제공하는 빈 후처리기, 하나의 프록시/여러 Advisor 적용 본문
빈 후처리기(소개/예제/적용/정리), 스프링이 제공하는 빈 후처리기, 하나의 프록시/여러 Advisor 적용
소소한나구리 2024. 11. 11. 16:42출처 : 인프런 - 스프링 핵심 원리 - 고급편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 빈 후처리기
1) 소개
(1) 일반적인 스프링 빈 등록
- @Bean이나 컴포넌트 스캔으로 스프링 빈을 등록하면 스프링은 대상 객체를 생성하고 스프링 컨테이너 내부의 빈 저장소에 등록함
- 이후에 스프링 컨테이너를 통해 등록한 스프링 빈을 조회해서 사용하면됨
(2) 빈 후처리기 - BeanPostProcessor
- 스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장소에 등록하기 직전에 조작하고 싶을 때 빈 후처리기를 사용
- 빈 포스트 프로세서(BeanPostProcessor)를 번역하여 빈 후처리기라고 하며, 빈을 생성한 후에 이미지의 2번(등록)의 상황에서 무언가 처리하는 용도로 사용함
- 빈 후처리의 기능은 매우 막강한데 객체를 조작하는 것을 넘어서 완전히 다른 객체로 바꿔치기 하는 것도 가능함
(3) 다른 객체로 바꿔치는 빈 후처리기
- 1. 생성: 스프링 빈 대상이 되는 객체를 생성함(@Bean, @ComponentScan모두 포함)
- 2. 전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달
- 3. 후 처리 작업: 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기 할 수 있음
- 4. 등록: 빈 후처리기는 빈을 반환함, 전달된 빈을 그대로 반환하면 빈이 등록되고 바꿔치기하면 다른 객체가 빈 저장소에 등록됨
2) 예제1 - 일반적인 스프링빈 등록 과정
(1) BasicTest
- test하위에 postprocessor패키지를 생성 후 작성
- new AnnotationConfigApplicationContext(BasicConfig.class)로 스프링 컨테이너를 생성하면서 BasicConfig.class를 넘겨주었기 때문에 설정 파일이 스프링 빈으로 등록됨
- 아래의 테스트 케이스를 작성하면 너무도 당연하게도 A는 스프링 빈으로 등록되어 메서드를 출력할 수 있지만, B는 스프링 빈으로 등록되지 않았기 때문에 NoSuchBeanDefinitionException 예외가 발생함
package hello.proxy.postprocessor;
public class BasicTest {
@Test
void basicConfig() {
ApplicationContext ac = new AnnotationConfigApplicationContext(BasicConfig.class);
// A는 빈으로 등록됨
A a = ac.getBean("beanA", A.class);
a.helloA();
// B는 빈으로 등록안됨
assertThatThrownBy(() -> ac.getBean(B.class)).isInstanceOf(NoSuchBeanDefinitionException.class);
}
@Slf4j
@Configuration
static class BasicConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
}
@Slf4j
static class A {
public void helloA() {
log.info("helloA");
}
}
@Slf4j
static class B {
public void helloB() {
log.info("helloB");
}
}
}
3) 예제2 - 빈 후처리기 사용
- 빈 후처리기를 통해서 A객체를 B객체로 바꾸기
(1) BeanPostProcessor 인터페이스 - 스프링 제공
- 빈 후처리기를 사용하려면 BeanPostProcessor인터페이스를 구현하고 스프링 빈으로 등록하면되며 라이프 사이클에 따라 다르게 호출되는 두가지 메서드가 있음
- postProcessBeforeInitialization: 객체 생성 이후에 @PostConstruct 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서
- postProcessAfterInitialization: 객체 생성 이후에 @PostConstruct 같은 초기화가 발생한 이후에 호출되는 포스트 프로세서
package org.springframework.beans.factory.config;
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;
}
}
(2) BeanPostProcessorTest
- AToBPostProcessor: BeanPostProcessor인터페이스를 구현한 빈 후처리기, 스프링 빈으로 등록하면 스프링 컨테이너가 빈 후처리기로 인식하고 동작함
- 파라미터로 넘어온 bean객체가 A객체이면 새로운 B객체로 바꿔서 반환되도록 동작하여 B가 스프링 컨테이너에 등록됨
- 테스트를 실행해보면 AtoBPostProcessor로 넘어온빈의 이름은 beanA, Bean은 A가 넘어가지만 후처리기에서 A가 B로 변경되어 최종적으로는 B가 반환되었고 A는 스프링빈으로 등록조차 되지 않음
package hello.proxy.postprocessor;
public class BeanPostProcessorTest {
@Test
void basicConfig() {
ApplicationContext ac = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);
// beanA의 이름으로 B 객체가 빈으로 등록됨
B b = ac.getBean("beanA", B.class);
b.helloB();
// A는 빈으로 등록안됨
assertThatThrownBy(() -> ac.getBean(A.class)).isInstanceOf(NoSuchBeanDefinitionException.class);
}
@Slf4j
@Configuration
static class BeanPostProcessorConfig {
@Bean(name = "beanA")
public A a() {
return new A();
}
@Bean
public AToBPostProcessor helloPostProcessor() {
return new AToBPostProcessor();
}
}
// classA, classB 코드 동일
@Slf4j
static class AToBPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("beanName={} bean={}", beanName, bean);
// bean에 A가 들어오면 B를 생성해서 반환
if (bean instanceof A) {
return new B();
}
return bean;
}
}
}
/*
10:57:02.075 [Test worker] WARN org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker -- Bean 'beanPostProcessorTest.BeanPostProcessorConfig' of type [hello.proxy.postprocessor.BeanPostProcessorTest$BeanPostProcessorConfig$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [helloPostProcessor] is declared through a non-static factory method on that class; consider declaring it as static instead.
10:57:02.082 [Test worker] INFO hello.proxy.postprocessor.BeanPostProcessorTest$AToBPostProcessor -- beanName=beanA bean=hello.proxy.postprocessor.BeanPostProcessorTest$A@6865c751
10:57:02.091 [Test worker] INFO hello.proxy.postprocessor.BeanPostProcessorTest$B -- helloB
*/
(3) 정리
- 빈 후처리기는 빈을 조작하고 변경할 수 있는 후킹 포인트이며 빈 객체를 조작하거나 심지어 다른 객체로 바꾸어 버릴 수 있음
- 여기서 조작이라는 것은 해당 객체의 특정 메서드를 호출하는 것을 뜻함
- 일반적으로 스프링컨테이너가 등록하는(특히 컴포넌트 스캔의 대상이 되는)빈들은 중간에 조작할 방법이 없는데 빈 후처리기를 사용하면 개발자가 등록하는 모든 빈을 중간에 조작할 수 있음
- 즉, 빈 객체를 프록시로 교체하는 것도 가능하다는 뜻임
** 참고 - @PostConstruct의 비밀
- @PostConstruct는 스프링 빈 생성 이후에 빈을 초기화하는 역할을 하는데 빈의 초기화라는 것은 단순히 @PostConstruct 애노테이션이 붙은 초기화메서드를 한번 호출만 하면됨
- 쉽게 말해 생성된 빈을 한번 조작하는 것이며, 빈을 조작하는 적절한 빈 후처리기가 있다는 뜻임
- 스프링은 CommonAnnotationBeanPostProcessor라는 빈 후처리기를 자동으로 등록하는데 여기에서 @PostConstruct애노테이션이 붙은 메서드를 호출함
- 즉, 스프링 스스로도 스프링 내부의 기능을 확장하기 위해 빈 후처리기를 사용함
4) 빈 후처리기 - 적용
(1) 적용 그림
- 빈 후처리기를 사용해서 실제 객체 대신 프록시를 스프링빈으로 등록하여 수동으로 등록하는 빈과 컴포넌트 스캔을 사용하는 빈까지 모두 프록시를 적용
- 더 나아가서 설정 파일에 있는 수많은 프록시 생성코드도 한번에 제거가 가능함
(2) PackageLogTraceProxyPostProcessor - 빈후처리기 생성
- config하위 패키지에 v4_postprocessor.postprocessor 패키지를 생성하여 코드를 작성
- 원본 객체를 프록시 객체로 바꿔주는 역할을 하는 후처리기이며, 프록시 생성은 프록시 팩토리를 사용하는데 프록시 팩토리는 advisor가 필요하기 때문에 advisor를 외부에서 주입받도록 작성
- 빈이 모두 생성된 이후에 후처리기가 동작하도록 postProcessAfterInitialization()를 구현
- 모든 스프링 빈들에 프록시를 적용할 필요는 없으므로 특정 패키지와 그 하위에 위치한 스프링 빈들만 프록시를 적용할 수 있도록 basePackage 변수를 선언하여 파라미터로 넘어온 bean의 패키지이름과 외부 주입받은 basePackage가 다르면 프록시가 적용되지 않고 원본 객체를 그대로 반환하도록 작성
- 프록시 적용 대상의 반환값은 프록시 팩토리로 생성된 프록시를 반환하여 스프링 컨테이너에 원본 객체 대신에 프록시 객체가 스프링빈으로 등록되며 원본 객체는 스프링 빈으로 등록되지 않음
package hello.proxy.config.v4_postprocessor.postprocessor;
@Slf4j
public class PackageLogTraceProxyPostProcessor implements BeanPostProcessor {
private final String basePackage;
private final Advisor advisor;
public PackageLogTraceProxyPostProcessor(String basePackage, Advisor advisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("param beanName={} bean={}", beanName, bean.getClass());
// 프록시 대상 여부를 체크 후 프록시 적용 대상이 아니면 원본을 그대로 진행
String packageName = bean.getClass().getPackage().getName();
// 지정한 패키지의 bean이 아니면 bean을 그대로 반환
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;
}
}
(3) BeanPostProcessorConfig - 설정파일 생성
- @Import()로 V1과 V2를 수동 빈 등록하는 설정파일들을 빈등록, V3는 컴포넌트 스캔으로 자동으로 스프링 빈으로 등록되기 때문에 설정파일이 없어도 됨
- ProxyApplication에서 @Import로 한꺼번에 등록해도 되며 예제에서는 설정파일의 구분을 위해서 편의상 여기에 등록하였음
- @Bean logTraceProxyPostProcessor : 특정 패키지를 기준으로 프록시를 생성하는 빈 후처리기를 스프링 빈으로 등록하고, 빈 후처리기를 생성할 때 프록시를 적용할 패키지 정보(hello.proxy.app)과 어드바이저(getAdvisor(logTrace))를 넘겨줌
- getAdvisor()메서드에서 포인트컷과 어드바이스를 정의하여 어드바이저를 반환
- 설정파일을 보면 프록시를 생성하는 코드가 설정파일에서 사라져 순수한 빈 등록만 고민하면 됨
- 프록시를 생성하고 스프링 빈으로 등록하는 것은 빈 후처리기가 모두 처리해줌
package hello.proxy.config.v4_postprocessor;
@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class}) // v3는 ComponentScan 사용으로 설정등록을 안해도 됨
public class BeanPostProcessorConfig {
@Bean
public PackageLogTraceProxyPostProcessor logTraceProxyPostProcessor(LogTrace logTrace) {
return new PackageLogTraceProxyPostProcessor("hello.proxy.app", getAdvisor(logTrace));
}
private Advisor getAdvisor(LogTrace logTrace) {
// pointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*");
// advice
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
}
(4) ProxyApplication 수정 및 실행
- 애플리케이션에서 최종적으로 설정파일을 @Import하여 애플리케이션을 실행하면 스프링부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기를 통과하는 로그를 확인할 수 있음
- 여기에 모두 프록시를 적용하는것은 올바르지 않기 때문에 필요한 곳에 프록시를 적용하도록 basePackage 변수를 활용해서 v1 ~ v3 애플리케이션 관련 빈들만 프록시 적용 대상이 되도록 했음
- 로그를 확인해보면 v1은 인터페이스가 있으므로 JDK 동적 프록시가 적용되고, v2, v3는 구체 클래스만 있어 CGLIB 프록시가 적용되었음
- v1,v2,v3의 매핑된 url로 요청 파라미터를 보내보면 수동으로 등록한 빈 뿐만아니라 컴포넌트 스캔을 통해 등록한 v3 빈들도 프록시가 적용되어 정상적으로 로그 추적기가 동작하는 로그를 확인할 수 있음
@Import(BeanPostProcessorConfig.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app.v3") //주의
public class ProxyApplication {
// 기존 코드 생략
}
(5) 프록시 적용 대상 여부 체크
- 애플리케이션을 실행한 로그를 확인해보면 알겠지만 직접 등록한 스프링 빈들 뿐만 아니라 스프링 부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기에 넘어옴
- 그래서 어떤 빈을 프록시로 만들 것인지 기준이 필요하며 여기서는 간단히 basePackage를 사용하여 특정 패키지를 기준으로 해당 패키지와 그 하위 패키지의 빈들을 프록시를 만듦
- 스프링 부트가 기본으로 제공하는 빈 중에는 프록시 객체를 만들 수 없는 빈들도 있기에 모든 객체를 프록시를 만들 경우 오류가 발생하여 꼭 프록시를 만들 대상을 구분해주어야 하며, 스프링은 이를 훨씬 편리하게 해결하는 기능을 제공함
5) 빈 후처리기 - 정리
(1) 문제 해결
- 빈 후처리기 덕분에 프록시를 생성하는 부분을 하나로 집중할 수 있음
- 컴포넌트 스캔처럼 스프링이 직접 대상을 빈으로 등록하는 경우에도 중간에 빈 등록 과정을 가로채서 원본 대신에 프록시를 스프링 빈으로 등록할 수 있음
- 덕분에 애플리케이션에 수 많은 스프링빈이 추가되어도 프록시와 관련된 코드는 전혀 변경하지 않아도되어 많았던 설정 코드가 줄어듦
- 그리고 컴포넌트 스캔을 사용해도 프록시가 모두 적용됨
** 중요
- 프록시의 적용 대상 여부를 여기서는 간단히 패키지를 기준으로 설정했는데 포인트컷을 사용하는 것이 더 깔끔하게 적용할 수 있음
- 포인트컷은 이미 클래스, 메서드 단위의 필터 기능을 가지고 있기 때문에 프록시 적용 대상 여부를 정밀하게 설정할 수 있음
- 어드바이저는 포인트것을 이미 가지고 있기 때문에 어드바이저를 통해 포인트컷을 확인할 수 있으며 이후에 배울 스프링 AOP는 포인트컷을 사용해서 프록시 적용 대상 여부를 체크함
- 결과적으로 포인트컷은 아래의 두 곳에 사용됨
- 1. 프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용 (빈 후처리기 - 자동 프록시 생성)
- 2. 프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할 지 판단 (프록시 내부)
2. 스프링이 제공하는 빈 후처리기
1) 설정 및 설명
(1) build.gradle 설정 추가
- 해당 라이브러리를 추가하면 aspectjweaver라는 AspectJ관련 라이브러리를 등록하고 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록함
- 스프링 부트가 없던 시절에는 @EnableAspectJAutoProxy를 직접 상용해야 했는데 이 부분을 스프링 부트가 자동으로 처리해줌
- 스프링부트가 활성화하는 빈은 AopAutoConfiguration을 참고하면 되며 AspectJ는 뒤에서 설명함
implementation 'org.springframework.boot:spring-boot-starter-aop'
(2) 자동 프록시 생성기 - AutoProxyCreator
- 스프링 부트 자동 설정으로 AnnotationAwareAspectJAutoProxyCreator라는 빈 후처리기가 스프링 빈에 자동 등록되며 이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기임
- 해당 빈 후처리기는 스프링 빈으로 등록된 Advisor들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용해줌
- Advisor안에는 Pointcut과 Advice가 이미 모두 포함되어있으므로 Advisor만 알고 있으면 그 안에있는 Pointcut으로 어떤 스프링 빈에 프록시를 적용해야할지 알 수 있음
** 참고
- AnnotationAwareAspectJAutoProxyCreator는 @AspectJ와 관련된 AOP 기능도 자동으로 찾아서 처리해줌
- Advisor는 물론 @Aspect도 자동으로 인식해서 프록시를 만들고 AOP를 적용해줌, @Aspect에 대한 내용도 뒤에서 다룸
(3) 자동 프록시 생성기의 작동 과정
- 생성: 스프링이 스프링 빈 대상이 되는 객체를 생성
- 전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처라기에 전달
- 모든 Advisor빈 조회: 자동 프록시 생성기 - 빈후처리기가 스프링 컨테이너에서 모든 Advisor를 조회함
- 프록시 적용 대상 체크: 조회한 Advisor에 포함되어있는 포인트컷을 사용해서 해당 객체가 프록시를 적용할 대상인지 아닌지 판단, 이때 객체의 클래스 정보와 해당 객체의 모든 메서드를 포인트컷에 하나씩 매칭해보고 조건이 하나라도 만족하면 프록시 적용 대상이 됨, 즉 10개의 메서드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이됨
- 프록시 생성: 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록하고 프록시 적용 대상이 아니라면 원본 객체를 반환하여 원본 객체를 스프링 빈으로 등록함
- 빈 등록: 반환된 객체는 스프링 빈으로 등록됨
2) 적용
(1) AutoProxyConfig
- config패키지 하위에 v5_autoproxy 패키지를 생성 후 코드 작성
- 이미 라이브러리를 추가하여 스프링이 제공하는 빈 후처리기를 스프링 빈으로 등록했기때문에 설정파일에서는 어드바이저만 등록해주면 설정이 끝남
package hello.proxy.config.v5_autoproxy;
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AutoProxyConfig {
@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);
}
}
(2) ProxyApplication 수정 및 실행
- @Import로 위에서 작성한 설정파일을 등록한 후 실행 후 매핑된 URL로 요청 파라미터를 보내보면 정상적으로 로그 추적기가 동작하는 것을 확인할 수 있음
@Import(AutoProxyConfig.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app.v3") //주의
public class ProxyApplication {
// 기존 코드 생략
}
3) 중요 : 포인트 컷은 2가지에 사용됨 - 반복 강조
(1) 프록시 적용 여부 판단 - 생성 단계
- 자동 프록시 생성기는 포인트 컷을 사용해서 해당 빈이 프록시를 생성할 필요가 있는지 없는지 체크
- 클래스와 메서드 조건을 포인트컷 조건 하나하나와 매칭하여 조건에 맞는 것이 하나라도 있으면 프록시를 생성함
- 만약 조건에 맞는 것이 하나도 없으면 프록시를 생성할 필요가 없으므로 프록시를 생성하지 않음
- 예제에서 orderControllerV1은 request(), noLog()가 있는데 request()가 조건에 만족하므로 프록시를 생성함
(2) 어드바이스 적용 여부 판단 - 사용 단계
- 프록시가 호출되었을 때 부가 기능인 어드바이스를 적용할지 말지 포인트컷을 보고 판단함
- 예제에서 orderControllerV1은 이미 프록시가 걸려있고 request()는 포인트컷 조건에 만족하므로 프록시는 어드바이스를 먼저 호출하고 target을 호출함
- 그러나 noLog()는 포인트컷 조건에 만족하지 않으므로 어드바이스를 호출하지 않고 target만 호출함
** 참고
- 프록시를 모든 곳에 생성하는 것은 비용낭비이기 때문에 꼭 필요한 곳에 최소한의 프록시를 적용해야 함
- 그래서 자동 프록시 생성기는 모든 스프링 빈에 프록시를 적용하는 것이 아니라 포인트 컷으로 한번 필터링해서 어드바이스가 사용될 가능성이 있는 곳에만 프록시를 생성함
4) 적용2
(1) 애플리케이션 로딩 로그 확인
- 방금 적용된 애플리케이션의 로딩 로그를 보면 스프링이 초기화 되면서 기대하지 않은 로그들이 함께 출력됨
- 그 이유는 지금 사용한 포인트컷이 단순히 메서드 이름에 request*, order*, save*만 포함되어있으면 매칭 된다고 판단하기 때문임
- 스프링 내부에서 사용하는 빈에도 메서드 이름에 request, order, save라는 단어가 들어가있으면 프록시가 만들어지고 어드바이스가 적용이 되고있는 상황임
- 즉, 패키지에 메서드 이름까지 함께 지정할 수 있는 정밀한 포인트컷이 필요함
(2) AspectJExpressionPointcut
- AspectJ라는 AOP에 특화된 포인트컷 표현식을 적용할 수 있으며 실무에서는 이 포인트컷만 사용함
- 지금은 특별한 표현식으로 복잡한 포인트 컷을 만들 수 있다는 정도로만 이해하고 AspectJ포인트컷 표현식과 AOP에 대한 내용은 뒤에서 자세히 설명함
(3) AutoProxyConfig - advisor2 추가
- 기존에 등록한 advisor1의 @Bean은 주석처리하고 AspectJExpressionPointcut 포인트컷을 사용하는 advisor2를 스프링 빈으로 등록
- AspectJ포인트컷 표현식을 적용할 수 있으며 맨처음 *은 모든 반환타입, hello.proxy.app.. 은 해당 패키지와 그 하위 패키지, *(..)에서 *은 모든 메서드 이름, (..)은 파라미터는 상관없다는 뜻임
- 쉽게 말해 hello.proxy.app 패키지와 그 하위 패키지의 모든 메서드는 포인트컷의 매칭 대상이 된다는 뜻임
@Bean
public Advisor advisor2(LogTrace logTrace) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* hello.proxy.app..*(..))");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
(4) 실행 결과
- 해당 어드바이저를 사용하니 기대하지 않았던 로그들은 사라졌으나 /v1/no-log로 접속했을 때에도 로그가 출력됨
- advisor2에서 단순히 package를 기준으로 포인트 컷을 매칭했기 때문임
(5) AutoProxyConfig - advisor3 추가
- AspectJ 포인트컷 표현식을 &&로 조합하여 noLog는 포함되지 않도록 작성하여 적용
- advisor1과 advisor2에 있는 @Bean을 주석처리하고 애플리케이션을 실행 후 요청파라미터로 요청을 보내보면 no-log는 로그가 나오지 않는 것을 확인할 수 있음
@Bean
public Advisor advisor3(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);
}
3. 하나의 프록시, 여러 Advisor 적용
(1) 프록시 팩토리가 생성하는 프록시는 프록시를 하나만 생성함
- 스프링 빈이 advisor1, advisor2가 제공하는 포인트컷의 조건을 모두 만족하면 프록시 자동 생성기는 프록시를 하나만 생성함
- 프록시 팩토리가 생성하는 프록시는 내부에 여러 advisor들을 포함할 수 있기 때문에 프록시를 여러개 생성해서 낭비할 이유가 없음
(2) 프록시 자동 생성기 상황별 정리
- advisor1의 포인트컷만 만족 -> 프록시 1개 생성, 프록시에 advisor1만 포함
- advisor1, advisor2의 포인트컷을 모두 만족 -> 프록시 1개 생성, 프록시에 advisor1, advisor2 모두 포함
- advisor1, advisor2의 포인트컷을 모두 만족하지 않음 -> 프록시가 생성되지 않음
- 이후에 설명할 스프링 AOP도 위와 동일한 방식으로 동작함
(3) 정리
- 자동 프록시 생성기인 AnnotationAwareAspectJAutoProxyCreator 덕분에 개발자는 매우 편리하게 프록시를 적용할 수 있어 Advisor만 스프링 빈으로 등록하면 됨
- @Aspect 애노테이션을 사용하면 더 편리하게 포인트컷과 어드바이스를 만들고 프록시를 적용할 수 있음(다음강의)