Jak wiemy, Spring używa serwerów proxy w celu dodania funkcjonalności ( @Transactional
i @Scheduled
na przykład). Istnieją dwie opcje - użycie dynamicznego proxy JDK (klasa musi implementować niepuste interfejsy) lub wygenerowanie klasy potomnej za pomocą generatora kodu CGLIB. Zawsze myślałem, że proxyMode pozwala mi wybierać między dynamicznym proxy JDK a CGLIB.
Ale udało mi się stworzyć przykład, który pokazuje, że moje założenie jest błędne:
Przypadek 1:
Singel:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototyp:
@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;
}
}
Główny:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Wynik:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Tutaj widzimy dwie rzeczy:
MyBeanB
został utworzony tylko raz .- Aby dodać
@Transactional
funkcjonalnośćMyBeanB
, Spring użył CGLIB.
Przypadek 2:
Pozwól mi poprawić MyBeanB
definicję:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
W tym przypadku wynikiem jest:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Tutaj widzimy dwie rzeczy:
MyBeanB
został utworzony 3 razy.- Aby dodać
@Transactional
funkcjonalnośćMyBeanB
, Spring użył CGLIB.
Czy możesz wyjaśnić, co się dzieje? Jak naprawdę działa tryb proxy?
PS
Przeczytałem dokumentację:
/**
* 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
*/
ale nie jest to dla mnie jasne.
Aktualizacja
Przypadek 3:
Zbadałem jeszcze jeden przypadek, w którym wyodrębniłem interfejs z 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 {
iw tym przypadku wynikiem jest:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Tutaj widzimy dwie rzeczy:
MyBeanB
został utworzony 3 razy.- Aby dodać tę
@Transactional
funkcjonalnośćMyBeanB
, Spring użył dynamicznego proxy JDK.
MyBeanB
klasa nie rozszerza żadnych interfejsów, więc nic dziwnego, że twój dziennik konsoli pokazuje instancje proxy CGLIB. W przypadku 3 wprowadzenia i implementacji interfejsu otrzymujesz serwer proxy JDK. Opisujesz to nawet w tekście wprowadzającym.<aop:config proxy-target-class="true">
lub@EnableAspectJAutoProxy(proxyTargetClass = true)
.Odpowiedzi:
Proxy wygenerowane dla
@Transactional
zachowania służy innym celom niż zakresowe proxy.Serwer
@Transactional
proxy to taki, który otacza konkretny komponent bean, aby dodać zachowanie zarządzania sesją. Wszystkie wywołania metod wykonają zarządzanie transakcjami przed i po delegowaniu do faktycznego komponentu bean.Jeśli to zilustrujesz, to będzie wyglądać
Dla naszych celów możesz zasadniczo zignorować jego zachowanie (usuń
@Transactional
i powinieneś zobaczyć to samo zachowanie, z tym wyjątkiem, że nie będziesz mieć proxy cglib).Serwer
@Scope
proxy zachowuje się inaczej. Dokumentacja stwierdza:Spring naprawdę robi tworzenie definicji singleton bean dla typu fabryki reprezentującej serwer proxy. Odpowiedni obiekt proxy sprawdza jednak kontekst dla faktycznego komponentu bean dla każdego wywołania.
Jeśli to zilustrujesz, to będzie wyglądać
Ponieważ
MyBeanB
jest to prototypowy komponent bean, kontekst zawsze zwróci nową instancję.Dla celów tej odpowiedzi załóż, że odzyskałeś
MyBeanB
bezpośrednio za pomocąco jest zasadniczo tym, co robi Spring, aby osiągnąć
@Autowired
cel wstrzyknięcia.W twoim pierwszym przykładzie
Deklarujesz prototypową definicję komponentu bean (poprzez adnotacje).
@Scope
maproxyMode
element, któryZatem Spring nie tworzy zasięgowego proxy dla wynikowej fasoli. Możesz pobrać tę fasolę za pomocą
Masz teraz odniesienie do nowego
MyBeanB
obiektu utworzonego przez Spring. Jest to jak każdy inny obiekt Java, wywołania metod będą kierowane bezpośrednio do instancji, do której się odwołuje.Jeśli użyjesz go
getBean(MyBeanB.class)
ponownie, Spring zwróci nową instancję, ponieważ definicja komponentu bean dotyczy komponentu bean . Nie robisz tego, więc wszystkie wywołania metod są kierowane do tego samego obiektu.W twoim drugim przykładzie
deklarujesz zakresowy serwer proxy zaimplementowany przez cglib. Podczas zamawiania fasoli tego typu od Spring with
Spring wie, że
MyBeanB
jest to proxy o zasięgu i dlatego zwraca obiekt proxy, który spełnia APIMyBeanB
(tj. Implementuje wszystkie swoje metody publiczne), który wewnętrznie wie, jak pobrać rzeczywistą bean typuMyBeanB
dla każdego wywołania metody.Spróbuj uruchomić
Będzie to powrót
true
sugerując się tym, że wiosna jest zwrócenie obiektu Singleton proxy (nie prototyp Bean).Podczas wywoływania metody w implementacji proxy Spring użyje specjalnej
getBean
wersji, która wie, jak odróżnić definicję proxy od faktycznejMyBeanB
definicji komponentu bean. Zwróci to nowąMyBeanB
instancję (ponieważ jest to prototyp), a Spring przekaże do niej wywołanie metody poprzez odbicie (klasyczneMethod.invoke
).Twój trzeci przykład jest zasadniczo taki sam jak twój drugi.
źródło
context.getBean(MyBeanB.class)
, tak naprawdę nie dostajesz proxy, dostajesz rzeczywistą fasolę.@Autowired
pobiera proxy (w rzeczywistości nie powiedzie się, jeśli wstrzyknieszMyBeanB
zamiast typu interfejsu). Nie wiem, dlaczego Spring pozwala ci nagetBean(MyBeanB.class)
INTERFACES.@Transactional
. Za pomocą@Autowired MyBeanBInterface
serwerów proxy o zasięgu i zasięgach Spring wstrzykuje obiekt proxy. Jeśli jednak to zrobiszgetBean(MyBeanB.class)
, Spring nie zwróci proxy, zwróci docelową fasolę.