Как мы знаем, 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.
Не могли бы вы объяснить, что происходит? Как работает режим прокси?
PS
Я прочитал документацию:
/**
* 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-прокси. Вы даже описываете это в своем вступительном тексте.<aop:config proxy-target-class="true">
или@EnableAspectJAutoProxy(proxyTargetClass = true)
, соответственно.Ответы:
Прокси-сервер, сгенерированный для
@Transactional
поведения, служит иным целям, чем прокси-серверы с определенными областями.@Transactional
Прокси один , который оборачивает конкретный компонент , чтобы добавить поведение сеанса управления. Все вызовы методов будут выполнять управление транзакциями до и после делегирования фактическому компоненту.Если вы иллюстрируете это, это будет выглядеть так
Для наших целей вы можете по существу игнорировать его поведение (удалите,
@Transactional
и вы должны увидеть то же поведение, за исключением того, что у вас не будет прокси-сервера cglib).@Scope
Прокси ведет себя по- разному. В документации говорится:То, что на самом деле делает Spring, - это создание определения одиночного компонента для типа фабрики, представляющей прокси. Однако соответствующий прокси-объект запрашивает контекст для фактического компонента для каждого вызова.
Если вы иллюстрируете это, это будет выглядеть так
Поскольку
MyBeanB
это прототип bean, контекст всегда будет возвращать новый экземпляр.Для целей этого ответа предположим, что вы получили
MyBeanB
непосредственно сэто, по сути, то, что Spring делает для удовлетворения
@Autowired
цели инъекции.В вашем первом примере
Вы объявляете определение компонента-прототипа (через аннотации).
@Scope
имеетproxyMode
элемент, которыйТаким образом, Spring не создает прокси с заданной областью для получаемого компонента. Вы получаете этот боб с
Теперь у вас есть ссылка на новый
MyBeanB
объект, созданный Spring. Это подобно любому другому объекту Java, вызовы методов будут идти непосредственно к ссылочному экземпляру.Если вы используете
getBean(MyBeanB.class)
снова, Spring вернет новый экземпляр, поскольку определение bean-компонента предназначено для bean-компонента-прототипа . Вы этого не делаете, поэтому все ваши вызовы методов идут к одному и тому же объекту.Во втором примере
вы объявляете прокси-сервер с областью действия, который реализуется через cglib. При запросе bean этого типа из Spring с
Spring знает, что
MyBeanB
это прокси-сервер с областью действия, и поэтому возвращает прокси-объект, который удовлетворяет APIMyBeanB
(то есть реализует все свои открытые методы), который внутренне знает, как извлечь фактический компонент типаMyBeanB
для каждого вызова метода.Попробуйте запустить
Это вернет
true
намек на тот факт, что Spring возвращает одноэлементный прокси-объект (не bean-объект-прототип).При вызове метода внутри реализации прокси Spring будет использовать специальную
getBean
версию, которая знает, как отличить определение прокси от фактическогоMyBeanB
определения компонента. Это вернет новыйMyBeanB
экземпляр (так как это прототип), и Spring делегирует вызов метода через рефлексию (классикаMethod.invoke
).Ваш третий пример по сути такой же, как ваш второй.
источник
context.getBean(MyBeanB.class)
, вы на самом деле не получаете прокси, вы получаете фактический бин.@Autowired
получает прокси (на самом деле он потерпит неудачу, если вы введетеMyBeanB
вместо типа интерфейса). Я не знаю, почему Spring позволяет вам делатьgetBean(MyBeanB.class)
с интерфейсами.@Transactional
. С помощью@Autowired MyBeanBInterface
прокси-серверов и с областями видимости Spring будет вводить объект прокси. Если вы просто сделаете этоgetBean(MyBeanB.class)
, Spring не вернет прокси, он вернет целевой боб.