Zdarzają się sytuacje, w których z metody statycznej musisz uzyskać dostęp do ApplicationContext lub dowolnego innego beana Spring. W tym poradniku pokażę Ci jak można to zrobić.
Na wstępie zaznaczę, że zdecydowanie odradzam wstrzykiwanie beana w statyczne pole klasy. Ten artykuł jest przeznaczony dla tych, którzy są świadomi tego, co robią i nie mają innego wyjścia.
Zasoby
- GitHub: java-sandbox/autowire-bean-to-static-class
- YouTube:
Problem
Załóżmy, że mamy klasę pomocniczą o nazwie StaticUtils, podobną do poniższej:
import com.bettercoding.spring.config.MyConfig; import org.springframework.beans.factory.annotation.Autowired; public final class StaticUtils { @Autowired private static MyConfig myConfig; private StaticUtils() { } public static void printFoo() { System.out.println("Foo:" + myConfig.getFoo()); } }
Jak widać, w szóstej linii znajduje się adnotacja @Autowired i spodziewamy się, że Spring wstrzyknie tutaj odpowiedniego beana. Ale zwróć uwagę, że owe pole jest statyczne.
Poniżej znajduje się przykładowa klasa MyConfig używana w naszej klasie pomocniczej. Wymaga przekazania własności foo.
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; @Configuration public class MyConfig { @Value("${foo}") private String foo; public String getFoo() { return foo; } }
Napiszmy klasę aplikacji i przetestujmy ją. Pamiętaj, aby przekazać propertisa -Dfoo=bar.
import com.bettercoding.spring.utils.StaticUtils; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @SpringBootApplication public class MyApp { public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return new CommandLineRunner() { @Override public void run(String... args) throws Exception { System.out.println("Hello World"); StaticUtils.printFoo(); } }; } }
Wynik to NullPointerException.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.2.RELEASE) 2018-06-02 13:45:47.435 INFO 8077 --- [ main] com.bettercoding.spring.MyApp : Starting MyApp on bc-vbox with PID 8077 (/home/better-coding/Workspace/static-initializer/out/production/classes started by better-coding in /home/better-coding/Workspace/static-initializer) 2018-06-02 13:45:47.442 INFO 8077 --- [ main] com.bettercoding.spring.MyApp : No active profile set, falling back to default profiles: default 2018-06-02 13:45:47.539 INFO 8077 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b68b9a4: startup date [Sat Jun 02 13:45:47 CEST 2018]; root of context hierarchy 2018-06-02 13:45:48.495 INFO 8077 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2018-06-02 13:45:48.516 INFO 8077 --- [ main] com.bettercoding.spring.MyApp : Started MyApp in 1.689 seconds (JVM running for 2.339) Hello World 2018-06-02 13:45:48.519 INFO 8077 --- [ main] ConditionEvaluationReportLoggingListener : Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2018-06-02 13:45:48.524 ERROR 8077 --- [ main] o.s.boot.SpringApplication : Application run failed java.lang.IllegalStateException: Failed to execute CommandLineRunner at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:781) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] at com.bettercoding.spring.MyApp.main(MyApp.java:13) [classes/:na] Caused by: java.lang.NullPointerException: null at com.bettercoding.spring.utils.StaticUtils.printFoo(StaticUtils.java:19) ~[classes/:na] at com.bettercoding.spring.MyApp$1.run(MyApp.java:22) ~[classes/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:797) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE] ... 5 common frames omitted 2018-06-02 13:45:48.525 INFO 8077 --- [ main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b68b9a4: startup date [Sat Jun 02 13:45:47 CEST 2018]; root of context hierarchy 2018-06-02 13:45:48.527 INFO 8077 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown Process finished with exit code 1
Jak widać Spring nie jest w stanie wstrzykiwać beanów bezpośrednio do pól statycznych i wystąpił wyjątek NullPointerException.
Rozwiązanie: Wykorzystaj klasę StaticContextInitializer
Najbardziej elegancką i całkiem bezpieczną metodą jaką znam, jest ta, którą nazwałem StaticContextInitializer. Pomysł polega na stworzeniu komponentu o nazwie StaticContextInitializer. Jest on odpowiedzialny za inicjowanie klas statycznych poprzez metodę z adnotacją @PostConstruct. Zobaczmy to w praktyce.
Krok 1 – Zmiany w klasie pomocniczej
Edytuj klasę StaticUtils – usuń adnotację @Autowired i dodaj metodę ustawiającą dla pola myConfig. Klasa po zmianach powinna wyglądać jak poniżej.
import com.bettercoding.spring.config.MyConfig; public final class StaticUtils { private static MyConfig myConfig; public static void setMyConfig(MyConfig myConfig) { StaticUtils.myConfig = myConfig; } private StaticUtils() { } public static void printFoo() { System.out.println("Foo:" + myConfig.getFoo()); } }
Krok 2 – Tworzenie komponentu StaticContextInitializer
Utwórz klasę o nazwie StaticContextInitializer z metodą init z adnotacją @PostConstruct. Metoda init zostanie wykonana zaraz po utworzeniu instancji komponentu więc możemy ją wykorzystać aby ustawić statyczne pole myConfig w klasie StaticUtils.
import com.bettercoding.spring.config.MyConfig; import com.bettercoding.spring.utils.StaticUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class StaticContextInitializer { @Autowired private MyConfig myConfig; @Autowired private ApplicationContext context; @PostConstruct public void init() { StaticUtils.setMyConfig(myConfig); } }
Krok 3 – Test
Uruchom aplikację jeszcze raz, aby sprawdzić czy zachowuje się ona zgodnie z oczekiwaniami.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.2.RELEASE) 2018-06-02 14:04:21.993 INFO 8556 --- [ main] com.bettercoding.spring.MyApp : Starting MyApp on bc-vbox with PID 8556 (/home/better-coding/Workspace/static-initializer/out/production/classes started by better-coding in /home/better-coding/Workspace/static-initializer) 2018-06-02 14:04:21.998 INFO 8556 --- [ main] com.bettercoding.spring.MyApp : No active profile set, falling back to default profiles: default 2018-06-02 14:04:22.141 INFO 8556 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d36e4cd: startup date [Sat Jun 02 14:04:22 CEST 2018]; root of context hierarchy 2018-06-02 14:04:23.078 INFO 8556 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2018-06-02 14:04:23.093 INFO 8556 --- [ main] com.bettercoding.spring.MyApp : Started MyApp in 1.679 seconds (JVM running for 2.253) Hello World Foo:bar 2018-06-02 14:04:23.101 INFO 8556 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d36e4cd: startup date [Sat Jun 02 14:04:22 CEST 2018]; root of context hierarchy 2018-06-02 14:04:23.106 INFO 8556 --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown Process finished with exit code 0
To już wszystko co dla Ciebie przygotowałem w tym poradniku. Udostępnij proszę ten post aby pomóc mi dotrzeć do większego grona odbiorców. Dzięki i do zobaczenia w kolejnym poradniku.