@ Méthode automatique et statique

101

J'ai un @Autowiredservice qui doit être utilisé à partir d'une méthode statique. Je sais que c'est faux mais je ne peux pas changer la conception actuelle car cela demanderait beaucoup de travail, donc j'ai besoin d'un simple hack pour cela. Je ne peux pas changer randomMethod()pour être non statique et j'ai besoin d'utiliser ce bean câblé automatiquement. Des indices comment faire ça?

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

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}
Taks
la source
4
Une méthode statique ne peut pas référencer un champ non statique / d'instance.
Sotirios Delimanolis
18
c'est pourquoi j'ai créé ce fil, existe-t-il un moyen d'accéder à l'instance Autowired à partir de la méthode statique ...
Taks
Pourquoi l'utilisation de @Autowired en méthode statique est-elle incorrecte?
user59290

Réponses:

151

Vous pouvez le faire en suivant l'une des solutions:

Utilisation du constructeur @Autowired

Cette approche construira le bean nécessitant des beans comme paramètres de constructeur. Dans le code du constructeur, vous définissez le champ statique avec la valeur obtenue comme paramètre pour l'exécution du constructeur. Échantillon:

@Component
public class Boo {

    private static Foo foo;

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

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

Utiliser @PostConstruct pour transmettre la valeur au champ statique

L'idée ici est de remettre un bean à un champ statique après que le bean est configuré par le ressort.

@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
la source
3
est-ce une solution sûre?
Taks
2
J'ai utilisé la première solution et cela a fonctionné comme un charme, merci!
victorleduc
1
La première solution ne prend pas en charge l'utilisation de @Qualifier. Cela reste problématique si vous utilisez plusieurs référentiels.
user1767316
16
Qu'est-ce qui garantira que le constructeur est appelé avant l'accès à la méthode statique?
David Dombrowsky
2
La méthode init provoquera un bogue SonarQube car la méthode non statique modifie le champ statique.
jDub9
45

Vous devez contourner cela via une approche d'accesseur de contexte d'application statique:

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

}

Ensuite, vous pouvez accéder aux instances de bean de manière statique.

public class Boo {

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

}
Pavel Horal
la source
En fait, j'aime cette solution bien que je ne la comprenne pas complètement .. Im juste avoir la tête autour du ressort et j'ai besoin de refactoriser rapidement un morceau de code .. et c'est le problème du mélange statique avec autowired .. Quelle est la sécurité de cette solution?
Taks
2
Il est assez sûr si les appels statiques sont sous votre contrôle. L'aspect négatif le plus évident est qu'il peut arriver que vous appeliez getBeanavant l'initialisation du contexte (NPE) ou après la destruction du contexte avec ses beans. Cette approche a pour avantage que l'accès au contexte statique "laid" est inclus dans une méthode / classe.
Pavel Horal
1
Cela m'a sauvé la vie. C'est très utile par rapport à l'autre approche.
phoenix
6

Ce que vous pouvez faire est @Autowiredune méthode setter et lui faire définir un nouveau champ statique.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

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

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

Lorsque le bean sera traité, Spring injectera une Fooinstance d'implémentation dans le champ d'instance foo. Il injectera ensuite également la même Fooinstance dans la setStaticFoo()liste d'arguments, qui sera utilisée pour définir le champ statique.

Il s'agit d'une solution de contournement terrible et échouera si vous essayez d'utiliser randomMethod()avant que Spring n'ait traité une instance de Boo.

Sotirios Delimanolis
la source
utiliserait l'aide de @PostConstruct?
Taks
@Taks Bien sûr, cela fonctionne aussi. Sur setStaticFoo()ce, sans le Fooparamètre.
Sotirios Delimanolis
la question est de savoir si cela le rendrait plus sûr .. :) Je pensais que le printemps traiterait tout avant de nous permettre d'exécuter des méthodes.
Taks
1
@Taks La façon dont vous l'avez montré ne fonctionne pas (sauf si vous affichez un pseudo-code). Des indices comment faire ça? Les multiples réponses que vous avez sont des solutions de contournement, mais elles ont toutes le même problème que vous ne pouvez pas utiliser le champ statique jusqu'à ce que Spring traite votre classe (en fait le traitement d'une instance qui a un effet secondaire). En ce sens, ce n'est pas sûr.
Sotirios Delimanolis
3

Ça craint mais vous pouvez obtenir le bean en utilisant l' ApplicationContextAwareinterface. Quelque chose comme :

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
la source
0

Cela s'appuie sur la réponse de @ Pavel , pour résoudre la possibilité que le contexte Spring ne soit pas initialisé lors de l'accès à partir de la méthode getBean statique:

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

L'élément important ici est la initContextméthode. Cela garantit que le contexte sera toujours initialisé. Mais, notez que ce initContextsera un point de discorde dans votre code car il est synchronisé. Si votre application est fortement parallélisée (par exemple: le backend d'un site à fort trafic), cela peut ne pas être une bonne solution pour vous.

Hashken
la source
-2

Utilisez AppContext. Assurez-vous de créer un bean dans votre fichier de contexte.

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

public static void randomMethod() {
     foo.doStuff();
}
Vijay
la source
Qu'est-ce que c'est?? Quelle est la différence entre @Autowired et getBean
madhairsilence
Il est normal que vous ne puissiez pas transformer la classe en un @Component spring régulier, cela arrive souvent avec du code hérité.
carpinchosaurio