@ Metodo cablato e statico


100

Ho un @Autowiredservizio che deve essere utilizzato dall'interno di un metodo statico. So che è sbagliato, ma non posso cambiare il design attuale poiché richiederebbe molto lavoro, quindi ho bisogno di qualche semplice trucco per farlo. Non posso cambiare randomMethod()per essere non statico e devo usare questo bean autowired. Qualche indizio su come farlo?

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

public class Boo {
    @Autowired
    Foo foo;

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

4
Un metodo statico non può fare riferimento a un campo non statico / istanza.
Sotirios Delimanolis

18
ecco perché ho creato questo thread, esiste un modo in cui è possibile accedere all'istanza Autowired dall'interno del metodo statico ...
Taks

Perché l'utilizzo di @Autowired nel metodo statico è sbagliato?
user59290

Risposte:


151

Puoi farlo seguendo una delle soluzioni:

Utilizzando il costruttore @Autowired

Questo approccio costruirà il bean richiedendo alcuni bean come parametri del costruttore. All'interno del codice del costruttore si imposta il campo statico con il valore ottenuto come parametro per l'esecuzione del costruttore. Campione:

@Component
public class Boo {

    private static Foo foo;

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

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

Utilizzo di @PostConstruct per trasferire il valore al campo statico

L'idea qui è di consegnare un bean a un campo statico dopo che il bean è stato configurato entro la primavera.

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

3
è una soluzione sicura?
Prende il

2
Ho usato la prima soluzione e ha funzionato a meraviglia, grazie!
victorleduc

1
La prima soluzione non supporta l'uso di @Qualifier. Rimane problematico se si utilizzano diversi repository.
user1767316

15
Cosa garantirà che il costruttore venga chiamato prima dell'accesso al metodo statico?
David Dombrowsky

2
Il metodo init causerà un bug SonarQube perché il metodo non statico modifica il campo statico.
jDub9

45

È necessario risolvere questo problema tramite l'approccio della funzione di accesso al contesto dell'applicazione statica:

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

}

Quindi puoi accedere alle istanze del bean in modo statico.

public class Boo {

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

}

In realtà mi piace questa soluzione anche se non la capisco appieno .. Sto solo girando la testa intorno alla primavera e ho bisogno di rifattorizzare rapidamente un pezzo di codice .. e questo è il problema di mescolare statico con cablaggio automatico .. quanto è sicura questa soluzione?
Prende il

2
È abbastanza sicuro se le chiamate statiche sono sotto il tuo controllo. L'aspetto negativo più ovvio è che può accadere che getBeanvenga chiamato prima che il contesto venga inizializzato (NPE) o dopo che il contesto con i suoi bean è stato distrutto. Questo approccio ha il vantaggio che l'accesso al contesto statico "brutto" è racchiuso in un metodo / classe.
Pavel Horal

1
Questo mi ha salvato la vita. È molto utile rispetto all'altro approccio.
Phoenix

6

Quello che puoi fare è @Autowiredun metodo setter e impostare un nuovo campo statico.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

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

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

Quando il bean viene elaborato, Spring inietterà Fooun'istanza di implementazione nel campo dell'istanza foo. Quindi inietterà anche la stessa Fooistanza nell'elenco degli setStaticFoo()argomenti, che verrà utilizzato per impostare il campo statico.

Questa è una soluzione alternativa terribile e non riuscirà se si tenta di utilizzare randomMethod()prima che Spring abbia elaborato un'istanza di Boo.


l'uso di @PostConstruct sarebbe d'aiuto?
Prende il

@ Taks Sure, anche quello funziona. Cioè setStaticFoo(), senza il Fooparametro.
Sotirios Delimanolis

la domanda è se lo renderebbe più sicuro .. :) Ho pensato che la primavera avrebbe elaborato tutto prima di permetterci di eseguire qualsiasi metodo ..
Taks

1
@Taks Il modo in cui l'hai mostrato non funziona (a meno che tu non stia mostrando uno pseudo codice). Qualche indizio su come farlo? Le risposte multiple che hai ottenuto sono soluzioni alternative, ma hanno tutte lo stesso problema che non puoi usare il campo statico finché Spring non elabora la tua classe (in realtà elabora un'istanza che ha un effetto collaterale). In questo senso, non è sicuro.
Sotirios Delimanolis

3

Fa schifo ma puoi ottenere il fagiolo usando l' ApplicationContextAwareinterfaccia. Qualcosa di simile a :

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

0

Questo si basa sulla risposta di @ Pavel , per risolvere la possibilità che il contesto Spring non venga inizializzato quando si accede dal metodo statico 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 ();
  }
}

Il pezzo importante qui è il initContextmetodo. Assicura che il contesto venga sempre inizializzato. Tuttavia, tieni presente che initContextsarà un punto di contesa nel tuo codice mentre viene sincronizzato. Se la tua applicazione è fortemente parallelizzata (ad esempio: il backend di un sito ad alto traffico), questa potrebbe non essere una buona soluzione per te.


-2

Usa AppContext. Assicurati di creare un bean nel tuo file di contesto.

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

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

Cos'è questo?? Qual è la differenza tra @Autowired e getBean
madhairsilence

È normale quando non puoi trasformare la classe in una normale molla @Component, succede spesso con il codice legacy.
carpinchosaurio
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.