@Autowired und statische Methode

100

Ich habe einen @AutowiredDienst, der innerhalb einer statischen Methode verwendet werden muss. Ich weiß, dass dies falsch ist, aber ich kann das aktuelle Design nicht ändern, da es viel Arbeit erfordern würde. Deshalb brauche ich dafür einen einfachen Hack. Ich kann mich nicht ändern randomMethod(), um nicht statisch zu sein, und ich muss diese automatisch verdrahtete Bean verwenden. Irgendwelche Hinweise, wie das geht?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
quelle
4
Eine statische Methode kann nicht auf ein nicht statisches / Instanzfeld verweisen.
Sotirios Delimanolis
18
Aus diesem Grund habe ich diesen Thread erstellt. Gibt es eine Möglichkeit, auf die Autowired-Instanz mit einer statischen Methode zuzugreifen?
Taks
Warum ist die Verwendung von @Autowired in der statischen Methode falsch?
user59290

Antworten:

150

Sie können dies tun, indem Sie einer der folgenden Lösungen folgen:

Verwenden des Konstruktors @Autowired

Dieser Ansatz erstellt die Bean, für die einige Beans als Konstruktorparameter erforderlich sind. Innerhalb des Konstruktorcodes legen Sie das statische Feld mit dem Wert fest, der als Parameter für die Konstruktorausführung erhalten wurde. Stichprobe:

@Component
public class Boo {

    private static Foo foo;

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

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

Verwenden von @PostConstruct, um den Wert an ein statisches Feld zu übergeben

Die Idee hier ist, eine Bohne an ein statisches Feld zu übergeben, nachdem die Bohne bis zum Frühjahr konfiguriert wurde.

@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
quelle
3
Ist das eine sichere Lösung?
Taks
2
Ich habe die erste Lösung verwendet und es hat wie ein Zauber funktioniert, danke!
Victorleduc
1
Die erste Lösung unterstützt die Verwendung von @Qualifier nicht. Es bleibt problematisch, wenn mehrere Repositorys verwendet werden.
user1767316
14
Was garantiert, dass der Konstruktor aufgerufen wird, bevor auf die statische Methode zugegriffen wird?
David Dombrowsky
1
Die init-Methode verursacht einen SonarQube-Fehler, da die nicht statische Methode das statische Feld ändert.
jDub9
45

Sie müssen dies über den statischen Anwendungskontext-Accessor-Ansatz umgehen:

@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);
    }

}

Anschließend können Sie statisch auf Bean-Instanzen zugreifen.

public class Boo {

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

}
Pavel Horal
quelle
Ich mag diese Lösung tatsächlich, obwohl ich sie nicht vollständig verstehe. Ich bin gerade dabei, den Frühling in den Griff zu bekommen, und ich muss schnell ein Stück Code umgestalten. Und dies ist das Problem des Mischens von statischer mit automatisch verdrahteter Lösung. Wie sicher ist diese Lösung?
Taks
2
Es ist ziemlich sicher, wenn die statischen Anrufe unter Ihrer Kontrolle stehen. Der offensichtlichste negative Aspekt ist, dass es vorkommen kann, dass Sie aufrufen, getBeanbevor der Kontext initialisiert wird (NPE) oder nachdem der Kontext mit seinen Beans zerstört wurde. Dieser Ansatz hat den Vorteil, dass der "hässliche" statische Kontextzugriff in einer Methode / Klasse enthalten ist.
Pavel Horal
1
Das hat mir das Leben gerettet. Es ist sehr nützlich gegenüber dem anderen Ansatz.
Phoenix
6

Sie können @Autowiredeine Setter-Methode verwenden und ein neues statisches Feld festlegen.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

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

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

Wenn die Bean verarbeitet wird, fügt Spring eine FooImplementierungsinstanz in das Instanzfeld ein foo. Anschließend wird dieselbe FooInstanz auch in die setStaticFoo()Argumentliste eingefügt, mit der das statische Feld festgelegt wird.

Dies ist eine schreckliche Problemumgehung und schlägt fehl, wenn Sie versuchen, sie zu verwenden, randomMethod()bevor Spring eine Instanz von verarbeitet hat Boo.

Sotirios Delimanolis
quelle
würde die Verwendung von @PostConstruct helfen?
Taks
@ Taks Klar, das funktioniert auch. Auf setStaticFoo()das heißt, ohne den FooParameter.
Sotirios Delimanolis
Die Frage ist, ob es sicherer wird. :) Ich dachte, der Frühling würde alles verarbeiten, bevor wir irgendwelche Methoden ausführen können.
Taks
1
@Taks Die Art und Weise, wie Sie es gezeigt haben, funktioniert nicht (es sei denn, Sie haben Pseudocode angezeigt). Irgendwelche Hinweise, wie das geht? Die mehreren Antworten, die Sie erhalten haben, sind Problemumgehungen, aber alle haben das gleiche Problem, dass Sie das statische Feld erst verwenden können, wenn Spring Ihre Klasse verarbeitet (tatsächlich eine Instanz verarbeitet, die einen Nebeneffekt hat). In diesem Sinne ist es nicht sicher.
Sotirios Delimanolis
3

Es ist scheiße, aber Sie können die Bohne über die ApplicationContextAwareSchnittstelle erhalten. Etwas wie :

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
quelle
0

Dies baut auf der Antwort von @ Pavel auf , um die Möglichkeit zu beseitigen, dass der Spring-Kontext beim Zugriff über die statische getBean-Methode nicht initialisiert wird:

@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 ();
  }
}

Das wichtige Stück hier ist die initContextMethode. Es stellt sicher, dass der Kontext immer initialisiert wird. Beachten Sie jedoch, dass initContextdies ein Streitpunkt in Ihrem Code sein wird, wenn dieser synchronisiert wird. Wenn Ihre Anwendung stark parallelisiert ist (z. B. das Backend einer Site mit hohem Datenverkehr), ist dies möglicherweise keine gute Lösung für Sie.

Hashken
quelle
-2

Verwenden Sie AppContext. Stellen Sie sicher, dass Sie eine Bean in Ihrer Kontextdatei erstellen.

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

public static void randomMethod() {
     foo.doStuff();
}
Vijay
quelle
Was ist das?? Was ist der Unterschied zwischen @Autowired und getBean
madhairsilence
Es ist üblich, dass Sie die Klasse nicht in eine reguläre spring @Component verwandeln können. Dies passiert häufig mit Legacy-Code.
Carpinchosaurio