@ Método automático y estático

100

Tengo un @Autowiredservicio que debe usarse desde un método estático. Sé que esto está mal, pero no puedo cambiar el diseño actual, ya que requeriría mucho trabajo, por lo que necesito un truco simple para eso. No puedo cambiar randomMethod()para ser no estático y necesito usar este bean autowired. ¿Alguna pista de cómo hacer eso?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
fuente
4
Un método estático no puede hacer referencia a un campo de instancia / no estático.
Sotirios Delimanolis
18
es por eso que creé este hilo, ¿hay alguna manera de que se pueda acceder a la instancia Autowired desde dentro del método estático ...
Taks
¿Por qué es incorrecto usar @Autowired en el método estático?
user59290

Respuestas:

151

Puede hacer esto siguiendo una de las soluciones:

Usando el constructor @Autowired

Este enfoque construirá el bean requiriendo algunos beans como parámetros de constructor. Dentro del código del constructor, estableces el campo estático con el valor obtenido como parámetro para la ejecución del constructor. Muestra:

@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 el valor al campo estático

La idea aquí es entregar un bean a un campo estático después de que el bean esté configurado por Spring.

@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
fuente
3
¿Es esta una solución segura?
Toma el
2
Usé la primera solución y funcionó de maravilla, ¡gracias!
victorleduc
1
La primera solución no admite el uso de @Qualifier. Sigue siendo problemático si se utilizan varios repositorios.
user1767316
15
¿Qué garantizará que se llame al constructor antes de acceder al método estático?
David Dombrowsky
2
El método init causará un error en SonarQube porque el método no estático modifica el campo estático.
jDub9
45

Debe solucionar esto a través del enfoque de acceso al contexto de la aplicación estática:

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

}

Luego, puede acceder a las instancias de bean de manera estática.

public class Boo {

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

}
Pavel Horal
fuente
De hecho, me gusta esta solución, aunque no la entiendo del todo ... Estoy entrando en la primavera y necesito refactorizar rápidamente un fragmento de código ... y este es el problema de mezclar estática con autowired ... ¿qué tan segura es esta solución?
Toma el
2
Es bastante seguro si las llamadas estáticas están bajo su control. El aspecto negativo más obvio es que puede suceder que llame getBeanantes de que se inicialice el contexto (NPE) o después de que se destruya el contexto con sus beans. Este enfoque tiene la ventaja de que el acceso al contexto estático "feo" se incluye en un método / clase.
Pavel Horal
1
Esto me salvó la vida. Es muy útil sobre el otro enfoque.
phoenix
6

Lo que puede hacer es @Autowiredun método de establecimiento y hacer que establezca un nuevo 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();
    }
}

Cuando se procesa el bean, Spring inyectará una Fooinstancia de implementación en el campo de la instancia foo. Luego, también inyectará la misma Fooinstancia en la setStaticFoo()lista de argumentos, que se usará para establecer el campo estático.

Esta es una solución terrible y fallará si intenta usar randomMethod()antes de que Spring haya procesado una instancia de Boo.

Sotirios Delimanolis
fuente
¿Sería útil usar @PostConstruct?
Toma el
@Taks Claro, eso también funciona. Eso setStaticFoo()es, sin el Fooparámetro.
Sotirios Delimanolis
la pregunta es si lo haría más seguro .. :) Pensé que Spring procesaría todo antes de permitirnos ejecutar cualquier método ..
Taks
1
@Taks La forma en que lo mostró no funciona (a menos que mostrara un pseudocódigo). ¿Alguna pista de cómo hacer eso? Las múltiples respuestas que obtuvo son soluciones alternativas, pero todas tienen el mismo problema de que no puede usar el campo estático hasta que Spring procese su clase (en realidad, procese una instancia que tiene un efecto secundario). En ese sentido, no es seguro.
Sotirios Delimanolis
3

Apesta, pero puedes obtener el bean usando la ApplicationContextAwareinterfaz. 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
fuente
0

Esto se basa en la respuesta de @ Pavel , para resolver la posibilidad de que el contexto Spring no se inicialice al acceder desde el método estático 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 ();
  }
}

La pieza importante aquí es el initContextmétodo. Asegura que el contexto siempre se inicializará. Pero tenga en cuenta que initContextserá un punto de discordia en su código ya que está sincronizado. Si su aplicación está muy paralelizada (por ejemplo, el backend de un sitio de alto tráfico), es posible que esta no sea una buena solución para usted.

Hashken
fuente
-2

Utilice AppContext. Asegúrese de crear un bean en su archivo de contexto.

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

public static void randomMethod() {
     foo.doStuff();
}
Vijay
fuente
¿¿Que es esto?? ¿Cuál es la diferencia entre @Autowired y getBean?
madhairsilence
Es habitual cuando no puede convertir la clase en un @Component de primavera normal, sucede mucho con el código heredado.
Carpinchosaurio