Ciekawostka o tworzeniu beanów wykorzystując @Configuration
2024-08-13
Tworzenie beanów to odwieczna część pracy programisty, który “babra się” w ekosystemie Springa. Jednak nawet w tak rutynowej czynności można znaleźć coś ciekawego, czego niekoniecznie jest się świadomym. Przejdźmy zatem do sedna i spradźmy czym @Configuration może nas zaskoczyć. Jednak najpierw zarejestrujmy kilka beanów wykorzystując adnotację @Bean.
Czy my właśnie zarejestrowaliśmy beany w klasie oznaczonej adnotacją @Component? Zgadza się. Jest to jak najbardziej możliwe, ponieważ jeśli ktoś zajrzał do dokumetacji to na pewno dopatrzył się, że w adnotacji @Configuration znajduje się meta-adnotacja @Component. Czyli klasy konfiguracyjne to nic innego jak po prostu zwykłe beany. A to w jakiś sposób sprawia, że nawet klasa oznaczona przez @Component może służyć jako agregacja beanów niezbędnych do zarejestowania w kontenerze Springa.
Żeby przekonać się jak przygotowane przez nas wyżej rozwiązanie działa napiszmy sobie test sprawdzający czy kontekst Springa w ogóle wstał, a jeśli tak to co się stało z zarejestowanymi beanami.
Kontekst wstał jak najbardziej i ma się dobrze. Jednak patrząć w output coś powinno przykuć naszą uwagę.
1
2
just bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForComponent@5f0bab7e
aggregated bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForComponent@6b4125ed
Bean BaseClassForComponent oraz ten przypisany do pola w instancji klasy AggregationClassForComponent to dwie różne instancje! Takie zachowanie nie jest oczekiwane w kontekście aplikacji opartych o Springa. Beany domyślnie powinny być przecież singletonami! Zgadza się, ale to mamy zapewnione, gdy rejestrujemy beany poprzez wykorzystanie adnotacji @Component.
W powyższym przypadku, jeśli ktoś korzysta z IntelliJ to dla tej sytuacji dostanie ostrzeżenie znajdujące się przy wywołaniu metody baseClassForComponent - “Method annotated with @Bean is called directly. Use dependency injection instead.”. W ten sposób zostaliśmy pośrednio poinforowani o braku unikalności beana przed uruchomieniem aplikacji. Jak temu zaradzić? Oczywiście skorzystać z przeznaczonej do takiej funkcjonalności adnotacji @Configuration.
just bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForConfiguration@337a6d30
aggregated bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForConfiguration@337a6d30
Problem rozwiązany! Jednak wnikliwe osoby, które zaglądają do dokumentacji (albo dużo debugują…) dowiedzą się o czymś naprawdę ciekawym. No, bo w końcu jakim cudem wywołanie metody baseClassForConfiguration nie stworzyło nowej instancji tylko posłużyło się już wcześniej utworzoną instancją? Normalne zachowanie Javy powinno utworzyć nam dwa obiekty jak widzieliśmy to na przykładzie wyżej.
In common scenarios, @Bean methods are to be declared within @Configuration classes, ensuring that full configuration class processing applies and that cross-method references therefore get redirected to the container’s lifecycle management. This prevents the same @Bean method from accidentally being invoked through a regular Java method call, which helps to reduce subtle bugs that can be hard to track down.
Co oczywiście jest możliwe dzięki proxy tworzonemu w oparciu o CGLIB. Jeśli ktoś nie chce korzystać z takiego “dobrodziejstwa”, które dodaje narzut wydajnościowy na naszą aplikację podczas jej wstawania, to może skorzystać z atrybutu proxyBeanMethods znajdującego się w adnotacji @Configuration. Wystarczy ustawić tą flagę na false i problem z głowy!
just bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForConfiguration@6579cdbb
aggregated bean - pl.cezarysanecki.springdemotests.injectingdependencies.BaseClassForConfiguration@469a7575
No dobra, nie do końca… Wróciliśmy do pierwotnego podejścia z wykorzystaniem @Component jako klasy konfiguracyjnej. W tym podejściu cześć pracy została przerzucona na nas, deweloperów. Musimy zmienić swoje dotychczasowe przyzwyczajenie i nie korzystać z wywoływania metod podczas tworzenia beanów. Alternatywą do tego sposobu jest przekazywanie niezbędnych zależności przez parametr metody.
Teraz uruchamiając test dowiadujemy się, że aplikacja działa, a my mamy tylko jedną instancję klasy BaseClassForConfiguration. Dodatkowo żadne proxy dla klasy ConfigurationClass się nie utworzyło. Także pełen sukces!