Método @Autowired e estático

100

Tenho @Autowiredserviço que deve ser usado de dentro de um método estático. Sei que isso está errado, mas não posso alterar o design atual, pois isso exigiria muito trabalho, então preciso de um hack simples para isso. Não posso mudar randomMethod()para não estático e preciso usar este bean autowired. Alguma pista de como fazer isso?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
fonte
4
Um método estático não pode fazer referência a um campo não estático / instância.
Sotirios Delimanolis
18
é por isso que criei este tópico, há uma maneira que a instância do Autowired pode ser acessada de dentro do método estático ...
Taks
Por que usar @Autowired no método estático é errado?
user59290

Respostas:

151

Você pode fazer isso seguindo uma das soluções:

Usando o construtor @Autowired

Essa abordagem construirá o bean exigindo alguns beans como parâmetros do construtor. Dentro do código do construtor, você define o campo estático com o valor obtido como parâmetro para a execução do construtor. Amostra:

@Component
public class Boo {

    private static Foo foo;

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

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

Usando @PostConstruct para entregar o valor ao campo estático

A ideia aqui é entregar um bean a um campo estático depois que o bean for configurado na 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();
    }
}
Francisco Spaeth
fonte
3
esta é uma solução segura?
Taks de
2
Usei a primeira solução e funcionou perfeitamente, obrigado!
victorleduc
1
A primeira solução não suporta o uso de @Qualifier. Continua sendo problemático se usar vários Repositórios.
user1767316
15
O que garantirá que o construtor seja chamado antes que o método estático seja acessado?
David Dombrowsky de
2
O método init causará o bug do SonarQube porque o método não estático modifica o campo estático.
jDub9,
45

Você deve contornar isso por meio da abordagem de acessador de contexto de aplicativo estático:

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

}

Então, você pode acessar as instâncias do bean de maneira estática.

public class Boo {

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

}
Pavel Horal
fonte
Na verdade, gosto dessa solução, embora não a entenda totalmente ... Estou começando a pensar na primavera e preciso refatorar rapidamente algum trecho de código ... e esse é o problema de misturar estático com autowired ... Quão segura é essa solução?
Taks de
2
É bastante seguro se as chamadas estáticas estiverem sob seu controle. O aspecto negativo mais óbvio é que pode acontecer de você chamar getBeanantes que o contexto seja inicializado (NPE) ou depois que o contexto com seus beans for destruído. Essa abordagem tem a vantagem de que o acesso ao contexto estático "feio" é incluído em um método / classe.
Pavel Horal
1
Isso salvou minha vida. É muito útil sobre a outra abordagem.
Phoenix
6

O que você pode fazer é @Autowiredum método setter e definir um novo campo estático.

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 o bean for processado, o Spring injetará uma Fooinstância de implementação no campo de instância foo. Em seguida, ele também injetará a mesma Fooinstância na setStaticFoo()lista de argumentos, que será usada para definir o campo estático.

Esta é uma solução terrível e falhará se você tentar usar randomMethod()antes que o Spring tenha processado uma instância de Boo.

Sotirios Delimanolis
fonte
usaria @PostConstruct para ajudar?
Taks de
@Taks Claro, isso também funciona. Ou setStaticFoo()seja, sem o Fooparâmetro.
Sotirios Delimanolis
a questão é se isso o tornaria mais seguro .. :) Eu pensei que o spring processaria tudo antes de nos permitir executar qualquer método ..
Taks
1
@Taks A maneira como você mostrou não funciona (a menos que você estivesse mostrando um pseudo código). Alguma pista de como fazer isso? As múltiplas respostas que você obteve são soluções alternativas, mas todas têm o mesmo problema de que você não pode usar o campo estático até que o Spring processe sua classe (na verdade, processando uma instância que tem um efeito colateral). Nesse sentido, não é seguro.
Sotirios Delimanolis
3

É uma merda, mas você pode obter o bean usando a ApplicationContextAwareinterface. Algo como :

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

Isso se baseia na resposta de @Pavel, para resolver a possibilidade de o contexto Spring não ser inicializado ao acessar a partir do método getBean estático:

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

A peça importante aqui é o initContextmétodo. Isso garante que o contexto sempre será inicializado. Mas, observe que initContextserá um ponto de discórdia em seu código, pois ele está sincronizado. Se seu aplicativo for muito paralelizado (por exemplo: o back-end de um site de alto tráfego), essa pode não ser uma boa solução para você.

Hashken
fonte
-2

Use AppContext. Certifique-se de criar um bean em seu arquivo de contexto.

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

public static void randomMethod() {
     foo.doStuff();
}
Vijay
fonte
O que é isso?? Qual é a diferença entre @Autowired e getBean
madhairsilence
É comum quando você não pode transformar a classe em um spring regular @Component, isso acontece muito com o código legado.
carpinchosaurio