You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
packagehello.proxy.config.v4_postprocessor.postprocessor;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.aop.Advisor;
importorg.springframework.aop.framework.ProxyFactory;
importorg.springframework.beans.BeansException;
importorg.springframework.beans.factory.config.BeanPostProcessor;
@Slf4jpublicclassPackageLogTraceProxyPostProcessorimplementsBeanPostProcessor {
privatefinalStringbasePackage;
privatefinalAdvisoradvisor;
//생성자 외부에서 프록시를 적용할 기준 패키지와 적용할 Advisor 주입publicPackageLogTraceProxyPostProcessor(StringbasePackage, Advisoradvisor) {
this.basePackage = basePackage;
this.advisor = advisor;
}
@OverridepublicObjectpostProcessAfterInitialization(Objectbean, StringbeanName) throwsBeansException {
log.info("param beanName={} bean={}", beanName, bean.getClass());
//프록시 적용 대상 여부 체크//프록시 적용 대상이 아니면 원본을 그대로 반환StringpackageName = bean.getClass().getPackageName();
if(!packageName.startsWith(basePackage)) {
returnbean; // 지정한 basePackage가 아니면 프록시 적용하지 않음
}
//프록시 대상이면 프록시를 만들어서 반환ProxyFactoryproxyFactory = newProxyFactory(); //프록시 객체 생성proxyFactory.addAdvisor(advisor); //advisor는 어떤 메서드에 어떤 부가기능을 적용할지 지정도니 AOP 설정Objectproxy = proxyFactory.getProxy();
log.info("create proxy: target={} proxy={}", bean.getClass(), proxy.getClass());
returnproxy;
}
}
PackageLogTraceProxyPostProcessor 는 원본 객체를 프록시 객체로 변환하는 역할을 한다. 이때 프록
시 팩토리를 사용하는데, 프록시 팩토리는 advisor 가 필요하기 때문에 이 부분은 외부에서 주입 받도록 했다.
모든 스프링 빈들에 프록시를 적용할 필요는 없다. 여기서는 특정 패키지와 그 하위에 위치한 스프링 빈들만 프록시를 적용한다. 여기서는 hello.proxy.app 과 관련된 부분에만 적용하면 된다. 다른 패키지의 객체들은 원본 객체를 그대로 반환한다.
프록시 적용 대상의 반환 값을 보면 원본 객체 대신에 프록시 객체를 반환한다. 따라서 스프링 컨테이너에 원본 객체 대신에 프록시 객체가 스프링 빈으로 등록된다. 원본 객체는 스프링 빈으로 등록되지 않는다.
BeanPostProcessorConfig
packagehello.proxy.config.v4_postprocessor;
importhello.proxy.config.AppV1Config;
importhello.proxy.config.AppV2Config;
importhello.proxy.config.v3_proxyfactory.advice.LogTraceAdvice;
importhello.proxy.config.v4_postprocessor.postprocessor.PackageLogTraceProxyPostProcessor;
importhello.proxy.config.v4_postprocessor.postprocessor.PackageLogTraceProxyPostProcessor;
importhello.proxy.trace.logtrace.LogTrace;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.aop.Advisor;
importorg.springframework.aop.support.DefaultPointcutAdvisor;
importorg.springframework.aop.support.NameMatchMethodPointcut;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.context.annotation.Import;
@Slf4j@Configuration@Import({AppV1Config.class, AppV2Config.class})
publicclassBeanPostProcessorConfig {
@Bean//프록시를 적용할 패키지 기준과 Advisor를 인자로 넘김publicPackageLogTraceProxyPostProcessorlogTraceProxyPostProcessor(LogTracelogTrace) {
//hello.proxy.app 패키지로 시작하는 클래스에만 프록시 적용//getAdvisor(logTrace)를 통해 메서드 기준 설정(AOP 규칙)을 넘김returnnewPackageLogTraceProxyPostProcessor("hello.proxy.app",
getAdvisor(logTrace));
}
privateAdvisorgetAdvisor(LogTracelogTrace) {
//pointcutNameMatchMethodPointcutpointcut = newNameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "save*"); //시작하는 이름의 메서드에만 AOP가 적용//advice//프록시가 호출될 때 실제로 적용할 부가기능LogTraceAdviceadvice = newLogTraceAdvice(logTrace);
//advisor = pointcut + advicereturnnewDefaultPointcutAdvisor(pointcut, advice);
}
}
@import({AppV1Config.class, AppV2Config.class}) : V3는 컴포넌트 스캔으로 자동으로 스프링 빈으로 등록되지만, V1, V2 애플리케이션은 수동으로 스프링 빈으로 등록해야 동작한다. ProxyApplication에서 등록해도 되지만 편의상 여기에 등록하자
@bean logTraceProxyPostProcessor() : 특정 패키지를 기준으로 프록시를 생성하는 빈 후처리기를 스
프링 빈으로 등록한다. 빈 후처리기는 스프링 빈으로만 등록하면 자동으로 동작한다. 여기에 프록시를 적용할 패키지 정보( hello.proxy.app )와 어드바이저( getAdvisor(logTrace) )를 넘겨준다.
이제 프록시를 생성하는 코드가 설정 파일에는 필요 없다. 순수한 빈 등록만 고민하면 된다. 프록시를 생성하고 프록시를 스프링 빈으로 등록하는 것은 빈 후처리기가 모두 처리해준다.
여기서는 생략했지만, 실행해보면 스프링 부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기를 통과하는 것을확인할 수 있다.
여기에 모두 프록시를 적용하는 것은 올바르지 않다. 꼭 필요한 곳에만 프록시를 적용해야 한다.
여기서는 basePackage 를 사용해서 v1~v3 애플리케이션 관련 빈들만 프록시 적용 대상이 되도록 했다.
v1: 인터페이스가 있으므로 JDK 동적 프록시가 적용된다.
v2: 구체 클래스만 있으므로 CGLIB 프록시가 적용된다.
v3: 구체 클래스만 있으므로 CGLIB 프록시가 적용된다.
컴포넌트 스캔에도 적용
여기서 중요한 포인트는 v1, v2와 같이 수동으로 등록한 빈 뿐만 아니라 컴포넌트 스캔을 통해 등록한 v3 빈들도 프록시를 적용할 수 있다는 점이다. 이것은 모두 빈 후처리기 덕분이다.
프록시 적용 대상 여부 체크
애플리케이션을 실행해서 로그를 확인해보면 알겠지만, 우리가 직접 등록한 스프링 빈들 뿐만 아니라 스프링 부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기에 넘어온다.
그래서 어떤 빈을 프록시로 만들 것인지 기준이 필요하다. 여기서는 간단히 basePackage 를 사용해서 특정 패키지를 기준으로 해당 패키지와 그 하위 패키지의 빈들을 프록시로 만든다.
스프링 부트가 기본으로 제공하는 빈 중에는 프록시 객체를 만들 수 없는 빈들도 있다. 따라서 모든 객체를 프록시로 만들 경우 오류가 발생한다.
빈 후처리기 - 정리
문제1 - 너무 많은 설정
프록시를 직접 스프링 빈으로 등록하는 ProxyFactoryConfigV1 , ProxyFactoryConfigV2 와 같은 설정 파일
은 프록시 관련 설정이 지나치게 많다는 문제가 있다.
예를 들어서 애플리케이션에 스프링 빈이 100개가 있다면 여기에 프록시를 통해 부가 기능을 적용하려면 100개의 프록시 설정 코드가 들어가야 한다. 무수히 많은 설정 파일 때문에 설정 지옥을 경험하게 될 것이다.
스프링 빈을 편리하게 등록하려고 컴포넌트 스캔까지 사용하는데, 이렇게 직접 등록하는 것도 모자라서, 프록시를 적용하는 코드까지 빈 생성 코드에 넣어야 했다.
문제2 - 컴포넌트 스캔
애플리케이션 V3처럼 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능했다.
왜냐하면 컴포넌트 스캔으로 이미 스프링 컨테이너에 실제 객체를 스프링 빈으로 등록을 다 해버린 상태이기 때문이다.
좀 더 풀어서 설명하자면, 지금까지 학습한 방식으로 프록시를 적용하려면, 원본 객체를 스프링 컨테이너에 빈으로 등록하는 것이 아니라 ProxyFactoryConfigV1 에서 한 것 처럼, 프록시를 원본 객체 대신 스프링 컨테이너에 빈으로 등록해야 한다.
그런데 컴포넌트 스캔은 원본 객체를 스프링 빈으로 자동으로 등록하기 때문에 프록시 적용이 불가능하다.
문제 해결
빈 후처리기 덕분에 프록시를 생성하는 부분을 하나로 집중할 수 있다. 그리고 컴포넌트 스캔처럼 스프링이 직접 대상을 빈으로 등록하는 경우에도 중간에 빈 등록 과정을 가로채서 원본 대신에 프록시를 스프링 빈으로 등록할 수 있다.
덕분에 애플리케이션에 수 많은 스프링 빈이 추가되어도 프록시와 관련된 코드는 전혀 변경하지 않아도 된다. 그리고 컴포넌트 스캔을 사용해도 프록시가 모두 적용된다.
중요
프록시의 적용 대상 여부를 여기서는 간단히 패키지를 기준으로 설정했다. 그런데 잘 생각해보면 포인트컷을 사용하면 더 깔끔할 것 같다.
포인트컷은 이미 클래스, 메서드 단위의 필터 기능을 가지고 있기 때문에, 프록시 적용 대상 여부를 정밀하게 설정 할 수 있다.
참고로 어드바이저는 포인트컷을 가지고 있다. 따라서 어드바이저를 통해 포인트컷을 확인할 수 있다.
뒤에서 학습하겠지만 스프링 AOP는 포인트컷을 사용해서 프록시 적용 대상 여부를 체크한다.
결과적으로 포인트컷은 다음 두 곳에 사용
프록시 적용 대상 여부를 체크해서 꼭 필요한 곳에만 프록시를 적용한다. (빈 후처리기 - 자동 프록시 생성)
프록시의 어떤 메서드가 호출 되었을 때 어드바이스를 적용할 지 판단한다. (프록시 내부)
스프링이 제공하는 빈 후처리기1
build.gradle - 추가 implementation 'org.springframework.boot:spring-boot-starter-aop
이 라이브러리를 추가하면 aspectjweaver 라는 aspectJ 관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련
클래스를 자동으로 스프링 빈에 등록한다.
스프링 부트가 없던 시절에는 @EnableAspectJAutoProxy 를 직접 사용해야 했는데, 이 부분을 스프링 부트가 자동으로 처리해준다.
aspectJ 는 뒤에서 설명한다. 스프링 부트가 활성화하는 빈은 AopAutoConfiguration 를 참고하자.
자동 프록시 생성기 - AutoProxyCreator
앞서 이야기한 스프링 부트 자동 설정으로 AnnotationAwareAspectJAutoProxyCreator 라는 빈 후처리
기가 스프링 빈에 자동으로 등록된다.
이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기이다.
이 빈 후처리기는 스프링 빈으로 등록된 Advisor 들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용해준다.
Advisor 안에는 Pointcut 과 Advice 가 이미 모두 포함되어 있다.
따라서 Advisor 만 알고 있으면 그 안에 있는 Pointcut 으로 어떤 스프링 빈에 프록시를 적용해야 할지 알 수 있다. 그리고 Advice 로 부가 기능을 적용하면 된다.
자동 프록시 생성기의 작동 과정 - 그림
생성: 스프링이 스프링 빈 대상이 되는 객체를 생성한다. ( @bean , 컴포넌트 스캔 모두 포함)
전달: 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달한다.
모든 Advisor 빈 조회: 자동 프록시 생성기 - 빈 후처리기는 스프링 컨테이너에서 모든 Advisor 를 조회한
다.
프록시 적용 대상 체크: 앞서 조회한 Advisor 에 포함되어 있는 포인트컷을 사용해서 해당 객체가 프록시를
적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 포인트컷에하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용 대상이 된다. 예를 들어서 10개의 메서드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.
프록시 생성: 프록시 적용 대상이면 프록시를 생성하고 반환해서 프록시를 스프링 빈으로 등록한다. 만약 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로 등록한다.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
빈 후처리기 - 예제 코드1
PackageLogTraceProxyPostProcessor
시 팩토리를 사용하는데, 프록시 팩토리는 advisor 가 필요하기 때문에 이 부분은 외부에서 주입 받도록 했다.
BeanPostProcessorConfig
프링 빈으로 등록한다. 빈 후처리기는 스프링 빈으로만 등록하면 자동으로 동작한다. 여기에 프록시를 적용할 패키지 정보( hello.proxy.app )와 어드바이저( getAdvisor(logTrace) )를 넘겨준다.
ProxyApplication
애플리케이션 로딩 로그
컴포넌트 스캔에도 적용
프록시 적용 대상 여부 체크
빈 후처리기 - 정리
문제1 - 너무 많은 설정
은 프록시 관련 설정이 지나치게 많다는 문제가 있다.
문제2 - 컴포넌트 스캔
문제 해결
중요
뒤에서 학습하겠지만 스프링 AOP는 포인트컷을 사용해서 프록시 적용 대상 여부를 체크한다.
결과적으로 포인트컷은 다음 두 곳에 사용
스프링이 제공하는 빈 후처리기1
build.gradle - 추가
implementation 'org.springframework.boot:spring-boot-starter-aop클래스를 자동으로 스프링 빈에 등록한다.
자동 프록시 생성기 - AutoProxyCreator
기가 스프링 빈에 자동으로 등록된다.
자동 프록시 생성기의 작동 과정 - 그림

다.
적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의 모든 메서드를 포인트컷에하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용 대상이 된다. 예를 들어서 10개의 메서드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.
생성된 프록시
AutoProxyConfig
AutoProxyConfig코드를 보면 advisor1이라는 어드바이저 하나만 등록( AnnotationAwareAspectJAutoProxyCreator ) 빈 후처리기를 자동으로 등록해준다.
중요: 포인트컷은 2가지에 사용
1. 프록시 적용 여부 판단 - 생성 단계
본다. 만약 조건에 맞는 것이 하나라도 있으면 프록시를 생성한다.
성한다.
2. 어드바이스 적용 여부 판단 - 사용 단계
참고
스프링이 제공하는 빈 후처리기2
애플리케이션 로딩 로그
AutoProxyConfig - advisor2 추가
advisor1에 있는@Bean은 꼭 주석처리해주어야 함, 그렇지 않음 어드바이저 중복 등록 됨AspectJExpressionPointcut: AspectJ 포인트컷 표현식을 적용할 수 있음"execution(* hello.proxy.app..*(..))": AspectJ가 제공하는 포인트컷 표현식그런데 문제는 이 부분에 로그가 출력된다. advisor2 에서는 단순히 package 를 기준으로 포인트컷 매칭을 했기 때문이다.
AutoProxyConfig advisor3 추가
주의
표현식 수정
execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))뜻이다.
Beta Was this translation helpful? Give feedback.
All reactions