우리가 알고 있듯이 봄이 기능을 추가하는 프록시를 사용하는 ( @Transactional
그리고 @Scheduled
예를 들어). JDK 동적 프록시 (클래스가 비어 있지 않은 인터페이스를 구현해야 함)를 사용하거나 CGLIB 코드 생성기를 사용하여 하위 클래스를 생성하는 두 가지 옵션이 있습니다. 나는 항상 proxyMode를 사용하여 JDK 동적 프록시와 CGLIB 중에서 선택할 수 있다고 생각했습니다.
그러나 나는 내 가정이 잘못되었음을 보여주는 예를 만들 수있었습니다.
사례 1 :
하나씩 일어나는 것:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
원기:
@Service
@Scope(value = "prototype")
public class MyBeanB {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional // just to force Spring to create a proxy
public long getCounter() {
return index;
}
}
본관:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
산출:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
여기서 우리는 두 가지를 볼 수 있습니다.
MyBeanB
오직 인스턴스화되었다 한 번만 .@Transactional
기능 을 추가하려면MyBeanB
Spring은 CGLIB를 사용했습니다.
사례 2 :
MyBeanB
정의를 수정하겠습니다 :
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
이 경우 출력은 다음과 같습니다.
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
여기서 우리는 두 가지를 볼 수 있습니다.
MyBeanB
3 인스턴스화되었습니다 번 되었습니다.- 에 대한
@Transactional
기능 을 추가하기 위해MyBeanB
Spring은 CGLIB를 사용했습니다.
무슨 일인지 설명해 주시겠습니까? 프록시 모드는 실제로 어떻게 작동합니까?
추신
설명서를 읽었습니다.
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
그러나 그것은 분명하지 않습니다.
최신 정보
사례 3 :
인터페이스를 추출한 사례를 한 번 더 조사했습니다 MyBeanB
.
public interface MyBeanBInterface {
long getCounter();
}
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
이 경우 출력은 다음과 같습니다.
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
여기서 우리는 두 가지를 볼 수 있습니다.
MyBeanB
3 인스턴스화되었습니다 번 되었습니다.- 의
@Transactional
기능 을 추가하기 위해MyBeanB
Spring은 JDK 동적 프록시를 사용했습니다.
답변
@Transactional
동작을 위해 생성 된 프록시 는 범위가 지정된 프록시와 다른 용도로 사용됩니다.
@Transactional
프록시는 세션 관리 동작을 추가 할 수있는 특정 빈을 감싸는 것입니다. 모든 메소드 호출은 실제 Bean에 위임하기 전후에 트랜잭션 관리를 수행합니다.
설명하면 다음과 같습니다
main -> getCounter -> (cglib-proxy -> MyBeanB)
우리의 목적을 위해, 당신은 본질적으로 그 행동을 무시할 수 있습니다 (제거 @Transactional
하고 cglib 프록시가 없다는 것을 제외하고는 동일한 행동을 보게됩니다).
@Scope
프록시는 다르게 동작합니다. 설명서에는 다음이 명시되어 있습니다.
[…] 범위가 지정된 객체와 동일한 공용 인터페이스를 노출 하지만 관련 범위 (예 : HTTP 요청) 에서 실제 대상 객체를 검색하고 메소드 호출을 실제 객체에 위임 할 수 있는 프록시 객체를 주입해야 합니다. .
Spring이 실제로하고있는 일은 프록시를 나타내는 팩토리 유형에 대한 싱글 톤 Bean 정의를 만드는 것입니다. 그러나 해당 프록시 오브젝트는 모든 호출에 대한 실제 Bean의 컨텍스트를 조회합니다.
설명하면 다음과 같습니다
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
때문에 MyBeanB
프로토 타입 콩은, 문맥은 항상 새로운 인스턴스를 반환합니다.
이 답변의 목적을 위해, 당신은을 검색 가정 MyBeanB
에 직접
MyBeanB beanB = context.getBean(MyBeanB.class);
이것은 본질적으로 Spring이 @Autowired
주입 목표 를 만족시키기 위해 수행하는 것입니다.
첫 번째 예에서
@Service
@Scope(value = "prototype")
public class MyBeanB {
어노테이션을 통해 프로토 타입 Bean 정의를 선언합니다 . @Scope
갖는 proxyMode
요소를 어느
구성 요소를 범위 프록시로 구성해야하는지 여부와 프록시가 인터페이스 기반인지 서브 클래스 기반인지를 지정합니다.
기본값은
ScopedProxyMode.DEFAULT
이며, 이는 구성 요소 스캔 명령 레벨에서 다른 기본값을 구성하지 않으면 범위 프록시를 작성 하지 않아야 함을 나타냅니다 .
따라서 Spring은 결과 Bean에 대한 범위 프록시를 작성하지 않습니다. 당신은 그 콩을 검색
MyBeanB beanB = context.getBean(MyBeanB.class);
MyBeanB
Spring에 의해 생성 된 새로운 객체에 대한 참조가 생겼습니다. 이것은 다른 Java 객체와 마찬가지로 메소드 호출이 참조 된 인스턴스로 직접 이동합니다.
getBean(MyBeanB.class)
다시 사용 하면 Bean 정의는 프로토 타입 Bean 이므로 Spring은 새 인스턴스를 리턴합니다 . 그렇게하지 않으므로 모든 메소드 호출이 동일한 객체로 이동합니다.
두 번째 예에서
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
cglib를 통해 구현 된 범위 프록시를 선언합니다. Spring 에서이 유형의 Bean을 요청할 때
MyBeanB beanB = context.getBean(MyBeanB.class);
Spring MyBeanB
은 이것이 범위가 지정된 프록시 임을 알고 있으므로 각 메소드 호출에 대한 MyBeanB
실제 유형의 Bean을 검색하는 방법을 내부적으로 알고 있는 API를 만족하는 프록시 오브젝트 (즉, 모든 공용 메소드를 구현 함)를 리턴합니다 MyBeanB
.
달리기
System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));
이것은 true
Spring이 프로토 타입 Bean이 아닌 싱글 톤 프록시 객체를 반환한다는 사실을 암시합니다.
프록시 구현 내에서 메소드 호출에서 Spring은 getBean
프록시 정의와 실제 MyBeanB
Bean 정의 를 구별하는 방법을 알고 있는 특수 버전을 사용합니다 . MyBeanB
프로토 타입이므로 새 인스턴스를 반환 하고 Spring은 리플렉션 (classic Method.invoke
)을 통해 메소드 호출을 위임합니다 .
세 번째 예는 기본적으로 두 번째 예와 동일합니다.