Прокси-сервер, сгенерированный для поведения @Transactional
, служит для других целей, чем прокси-серверы с областью действия.
Прокси-сервер @Transactional
- это тот, который упаковывает определенный бин для добавления поведения управления сеансом. Все вызовы методов будут выполнять управление транзакциями до и после делегирования фактическому компоненту.
Если вы проиллюстрируете это, оно будет выглядеть так:
main -> getCounter -> (cglib-proxy -> MyBeanB)
Для наших целей вы можете по существу игнорировать его поведение (удалите @Transactional
, и вы должны увидеть то же поведение, кроме того, что вы выигралиу меня нет прокси cglib).
Прокси @Scope
1015 * ведет себя по-разному. В документации говорится:
[...] вам нужно внедрить прокси-объект, который предоставляет тот же открытый интерфейс, что и объект области действия , но который также может извлечь реальный целевой объект из соответствующегоОбласть действия (например, HTTP-запрос) и вызовы методов делегата для реального объекта.
Что на самом деле делает Spring - это создание определения синглтон-компонента для типа фабрики, представляющей прокси. Однако соответствующий прокси-объект запрашивает контекст для фактического компонента для каждого вызова.
Если вы проиллюстрируете его, он будет выглядеть как
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
, поскольку MyBeanB
является прототипом компонентаконтекст всегда будет возвращать новый экземпляр.
Для целей этого ответа предположим, что вы извлекли MyBeanB
непосредственно с помощью
MyBeanB beanB = context.getBean(MyBeanB.class);
, что по сути то, что Spring делает для удовлетворения@Autowired
target инъекции.
В вашем первом примере
@Service
@Scope(value = "prototype")
public class MyBeanB {
Вы объявляете определение прототипа компонента (через аннотации). @Scope
имеет элемент proxyMode
, который
Указывает, должен ли компонент быть настроен как прокси-сервер с областью действия, и если да, должен ли прокси-сервербыть основанным на интерфейсе или подклассе.
По умолчанию ScopedProxyMode.DEFAULT
, что обычно означает, что прокси с областью действия не должен создаваться , если в инструкции сканирования компонента не было настроено другое значение по умолчанию. level.
Таким образом, Spring не создает прокси-сервер области действия для получаемого компонента. Вы получаете этот компонент с помощью
MyBeanB beanB = context.getBean(MyBeanB.class);
Теперь у вас есть ссылка на новый MyBeanB
объект, созданный Spring. Это подобно любому другому объекту Java, вызовы методов будут идти непосредственно к ссылочному экземпляру.
Если вы снова использовали getBean(MyBeanB.class)
, Spring вернет новый экземпляр, поскольку определение компонента предназначено для прототипа боб . Вы этого не делаете, поэтому все ваши вызовы методов идут к одному и тому же объекту.
Во втором примере
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
вы объявляете прокси-сервер с областью действия, реализованный черезCGLIB. При запросе bean-компонента этого типа из Spring с помощью
MyBeanB beanB = context.getBean(MyBeanB.class);
Spring знает, что MyBeanB
является прокси-сервером области действия, и поэтому возвращает прокси-объект, который удовлетворяет API MyBeanB
(т. Е. Реализует все свои открытыеметоды), который внутренне знает, как получить фактический компонент типа MyBeanB
для каждого вызова метода.
Попробуйте запустить
System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));
Это вернет true
намека на тот факт, что Spring возвращает одноэлементный прокси-объект (не bean-объект-прототип).
НаПри вызове метода внутри реализации прокси Spring будет использовать специальную версию getBean
, которая знает, как отличить определение прокси от фактического определения компонента MyBeanB
. Это вернет новый экземпляр MyBeanB
(поскольку он является прототипом), и Spring делегирует вызов метода через рефлексию (классика Method.invoke
).
Ваш третий пример по сути такой жекак твой второй.