Как мы знаем, Spring использует прокси для добавления функциональности (например, @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.
Не могли бы вы объяснить, что происходит? Как на самом деле работает прокси-режим?
P.S.
Я прочитал документацию:
/**
* 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.
MyBeanB
не расширяет никаких интерфейсов, поэтому неудивительно, что ваш журнал консоли показывает экземпляры прокси-сервера CGLIB. В случае 3 вы вводите и реализуете интерфейс, следовательно, вы получаете прокси-сервер JDK. Вы даже описываете это во вступительном тексте. - person kriegaex   schedule 03.10.2019<aop:config proxy-target-class="true">
или@EnableAspectJAutoProxy(proxyTargetClass = true)
соответственно. - person kriegaex   schedule 03.10.2019