Như chúng ta đã biết Spring sử dụng proxy để thêm chức năng ( @Transactional
và @Scheduled
ví dụ). Có hai tùy chọn - sử dụng proxy động JDK (lớp phải triển khai các giao diện không trống) hoặc tạo một lớp con bằng cách sử dụng trình tạo mã CGLIB. Tôi luôn nghĩ rằng proxyMode cho phép tôi chọn giữa proxy động JDK và CGLIB.
Nhưng tôi đã có thể tạo một ví dụ cho thấy giả định của tôi là sai:
Trường hợp 1:
Người độc thân:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Nguyên mẫu:
@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;
}
}
Chủ yếu:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Đầu ra:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Ở đây chúng ta có thể thấy hai điều:
MyBeanB
đã được khởi tạo chỉ một lần .- Để thêm
@Transactional
chức năng choMyBeanB
, Spring đã sử dụng CGLIB.
Trường hợp 2:
Hãy để tôi sửa MyBeanB
định nghĩa:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
Trong trường hợp này, đầu ra là:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Ở đây chúng ta có thể thấy hai điều:
MyBeanB
đã được khởi tạo 3 lần.- Để thêm
@Transactional
chức năng choMyBeanB
, Spring đã sử dụng CGLIB.
Bạn có thể giải thích những gì đang xảy ra? Làm thế nào để chế độ proxy thực sự hoạt động?
PS
Tôi đã đọc tài liệu:
/**
* 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
*/
nhưng nó không rõ ràng cho tôi
Cập nhật
Trường hợp 3:
Tôi đã điều tra thêm một trường hợp, trong đó tôi trích xuất giao diện từ 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 {
và trong trường hợp này, đầu ra là:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Ở đây chúng ta có thể thấy hai điều:
MyBeanB
đã được khởi tạo 3 lần.- Để thêm
@Transactional
chức năng choMyBeanB
, Spring đã sử dụng proxy động JDK.
MyBeanB
lớp của bạn không mở rộng bất kỳ giao diện nào, vì vậy không có gì đáng ngạc nhiên khi nhật ký bảng điều khiển của bạn hiển thị các phiên bản proxy CGLIB. Trong trường hợp 3 bạn giới thiệu và triển khai một giao diện, do đó bạn nhận được proxy JDK. Bạn thậm chí mô tả điều này trong văn bản giới thiệu của bạn.
<aop:config proxy-target-class="true">
hoặc @EnableAspectJAutoProxy(proxyTargetClass = true)
, tương ứng.