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.

 
 
							