Ответ 1
Прокси-сервер, сгенерированный для поведения @Transactional
, служит не так, как прокси-серверы с ограниченным доступом.
Прокси-сервер @Transactional
- это тот, который упаковывает конкретный компонент для добавления поведения управления сеансом. Все вызовы методов будут выполнять управление транзакциями до и после делегирования фактическому компоненту.
Если вы проиллюстрируете это, это будет выглядеть как
main -> getCounter -> (cglib-proxy -> MyBeanB)
В наших целях вы можете по существу игнорировать его поведение (удалите @Transactional
, и вы должны увидеть то же поведение, за исключением того, что у вас не будет прокси-сервера cglib).
Прокси-сервер @Scope
ведет себя по-разному. В документации говорится:
[...] вам нужно внедрить прокси-объект, который выставляет тот же общедоступный интерфейс как объект области , но это также может извлечь реальный целевой объект из соответствующей области (например, HTTP-запрос) и делегировать вызовы метода на реальный объект.
То, что на самом деле делает Spring, - это создание определения одиночного компонента для типа фабрики, представляющей прокси. Однако соответствующий прокси-объект запрашивает контекст фактического компонента для каждого вызова.
Если вы проиллюстрируете это, это будет выглядеть как
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 не создает прокси с заданной областью для получаемого компонента. Вы получаете этот боб с помощью
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
).
Ваш третий пример по сути такой же, как ваш второй.