@Autowired i metoda statyczna

101

Mam @Autowiredusługę, z której należy korzystać w ramach metody statycznej. Wiem, że to źle, ale nie mogę zmienić obecnego projektu, ponieważ wymagałoby to dużo pracy, więc potrzebuję do tego prostego hacka. Nie mogę zmienić się randomMethod()na niestatyczny i muszę użyć tej automatycznej fasoli. Jakieś wskazówki, jak to zrobić?

@Service
public class Foo {
    public int doStuff() {
        return 1;
    }
}

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
źródło
4
Metoda statyczna nie może odwoływać się do pola niestatycznego / instancji.
Sotirios Delimanolis
18
dlatego stworzyłem ten wątek, czy istnieje sposób, w jaki można uzyskać dostęp do instancji Autowired z poziomu metody statycznej ...
Taks
Dlaczego użycie @Autowired w metodzie statycznej jest nieprawidłowe?
user59290

Odpowiedzi:

152

Możesz to zrobić, wykonując jedno z rozwiązań:

Korzystanie z konstruktora @Autowired

To podejście spowoduje skonstruowanie fasoli wymagającej pewnych ziaren jako parametrów konstruktora. W kodzie konstruktora ustawiasz pole statyczne z wartością otrzymaną jako parametr do wykonania konstruktora. Próba:

@Component
public class Boo {

    private static Foo foo;

    @Autowired
    public Boo(Foo foo) {
        Boo.foo = foo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}

Użycie @PostConstruct do przekazania wartości do pola statycznego

Pomysł polega na przekazaniu fasoli na statyczne pole po skonfigurowaniu fasoli na wiosnę.

@Component
public class Boo {

    private static Foo foo;
    @Autowired
    private Foo tFoo;

    @PostConstruct
    public void init() {
        Boo.foo = tFoo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}
Francisco Spaeth
źródło
3
czy to jest bezpieczne rozwiązanie?
Trwa
2
Użyłem pierwszego rozwiązania i zadziałało jak urok, dzięki!
victorleduc
1
Pierwsze rozwiązanie nie obsługuje użycia @Qualifier. Pozostaje problematyczne w przypadku korzystania z kilku repozytoriów.
user1767316
16
Co zagwarantuje, że konstruktor zostanie wywołany przed uzyskaniem dostępu do metody statycznej?
David Dombrowsky
2
init spowoduje błąd SonarQube, ponieważ metoda niestatyczna modyfikująca pole statyczne.
jDub9
45

Musisz obejść ten problem za pomocą metody dostępu do kontekstu aplikacji statycznej:

@Component
public class StaticContextAccessor {

    private static StaticContextAccessor instance;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void registerInstance() {
        instance = this;
    }

    public static <T> T getBean(Class<T> clazz) {
        return instance.applicationContext.getBean(clazz);
    }

}

Następnie możesz uzyskać dostęp do instancji bean w sposób statyczny.

public class Boo {

    public static void randomMethod() {
         StaticContextAccessor.getBean(Foo.class).doStuff();
    }

}
Pavel Horal
źródło
Właściwie podoba mi się to rozwiązanie, chociaż nie do końca je rozumiem .. Po prostu łapię głowę na wiosnę i muszę szybko zreformować jakiś fragment kodu .. i to jest kwestia mieszania statycznego z autowredem .. jak bezpieczne jest to rozwiązanie?
Trwa
2
Jeśli statyczne wywołania są pod Twoją kontrolą, jest to całkiem bezpieczne. Najbardziej oczywistym negatywnym aspektem jest to, że może się zdarzyć, że zadzwonisz getBeanprzed zainicjowaniem kontekstu (NPE) lub po zniszczeniu kontekstu z jego fasolami. Takie podejście ma tę zaletę, że „brzydki” dostęp do kontekstu statycznego jest zawarty w jednej metodzie / klasie.
Pavel Horal
1
To uratowało mi życie. Jest to bardzo przydatne w porównaniu z innym podejściem.
phoenix
6

To, co możesz zrobić, to @Autowiredmetoda ustawiająca i ustawić nowe pole statyczne.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

    @Autowired
    public void setStaticFoo(Foo foo) {
        Boo.staticFoo = foo;
    }

    public static void randomMethod() {
         staticFoo.doStuff();
    }
}

Gdy ziarno zostanie przetworzone, Spring wstrzyknie Fooinstancję implementacji do pola instancji foo. Następnie wstrzyknie to samo Foowystąpienie do setStaticFoo()listy argumentów, która zostanie użyta do ustawienia pola statycznego.

Jest to okropne obejście i zakończy się niepowodzeniem, jeśli spróbujesz użyć go, randomMethod()zanim Spring przetworzy wystąpienie Boo.

Sotirios Delimanolis
źródło
użyje @PostConstruct help?
Trwa
@Taks Jasne, to też działa. Na setStaticFoo()to jest bez Fooparametru.
Sotirios Delimanolis
pytanie brzmi, czy to by było bezpieczniejsze .. :) Pomyślałem, że wiosna przetworzy wszystko, zanim pozwoli nam wykonać jakiekolwiek metody ..
Taks
1
@Taks Sposób, w jaki to pokazałeś, nie działa (chyba że pokazywałeś pseudokod). Jakieś wskazówki, jak to zrobić? Wiele odpowiedzi, które otrzymałeś, to obejścia, ale wszystkie mają ten sam problem, że nie możesz używać pola statycznego, dopóki Spring nie przetworzy Twojej klasy (w rzeczywistości przetwarza jedną instancję, która ma efekt uboczny). W tym sensie to nie jest bezpieczne.
Sotirios Delimanolis
3

To jest do bani, ale możesz zdobyć fasolę za pomocą ApplicationContextAwareinterfejsu. Coś jak :

public class Boo implements ApplicationContextAware {

    private static ApplicationContext appContext;

    @Autowired
    Foo foo;

    public static void randomMethod() {
         Foo fooInstance = appContext.getBean(Foo.class);
         fooInstance.doStuff();
    }

    @Override
    public void setApplicationContext(ApplicationContext appContext) {
        Boo.appContext = appContext;
    }
}
Jean-Philippe Bond
źródło
0

Opiera się to na odpowiedzi @ Pavel , aby rozwiązać problem braku inicjalizacji kontekstu Spring podczas uzyskiwania dostępu ze statycznej metody getBean:

@Component
public class Spring {
  private static final Logger LOG = LoggerFactory.getLogger (Spring.class);

  private static Spring spring;

  @Autowired
  private ApplicationContext context;

  @PostConstruct
  public void registerInstance () {
    spring = this;
  }

  private Spring (ApplicationContext context) {
    this.context = context;
  }

  private static synchronized void initContext () {
    if (spring == null) {
      LOG.info ("Initializing Spring Context...");
      ApplicationContext context = new AnnotationConfigApplicationContext (io.zeniq.spring.BaseConfig.class);
      spring = new Spring (context);
    }
  }

  public static <T> T getBean(String name, Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(name, className);
  }

  public static <T> T getBean(Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(className);
  }

  public static AutowireCapableBeanFactory getBeanFactory() throws IllegalStateException {
    initContext();
    return spring.context.getAutowireCapableBeanFactory ();
  }
}

Ważnym elementem jest tutaj initContextmetoda. Zapewnia, że ​​kontekst zawsze zostanie zainicjowany. Ale pamiętaj, że initContextbędzie to punkt sporny w twoim kodzie, ponieważ jest zsynchronizowany. Jeśli Twoja aplikacja jest mocno zrównoleglona (na przykład: zaplecze witryny o dużym ruchu), może to nie być dobre rozwiązanie dla Ciebie.

Hashken
źródło
-2

Użyj AppContext. Upewnij się, że tworzysz fasolę w swoim pliku kontekstowym.

private final static Foo foo = AppContext.getApplicationContext().getBean(Foo.class);

public static void randomMethod() {
     foo.doStuff();
}
Vijay
źródło
Co to jest?? Jaka jest różnica między @Autowired i getBean
madhairsilence
To normalne, że nie można zamienić klasy w zwykłą sprężynę @Component, często dzieje się to ze starszym kodem.
carpinchosaurio