Pourquoi la méthode @PostConstruct n'est pas appelée lors de l'autowiring prototype bean avec argument de constructeur
j'ai un prototype-scope bean, que je veux être injecté par @Autowired annotation. Dans ce haricot, il y a aussi la méthode @PostConstruct qui n'est pas appelée au printemps et je ne comprends pas pourquoi.
Mon haricot définition:
package somepackage;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@Scope("prototype")
public class SomeBean {
public SomeBean(String arg) {
System.out.println("Constructor called, arg: " + arg);
}
@PostConstruct
private void init() {
System.out.println("Post construct called");
}
}
JUnit classe où je veux injecter haricot:
package somepackage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:applicationContext-test.xml")
public class SomeBeanTest {
@Autowired
ApplicationContext ctx;
@Autowired
@Value("1")
private SomeBean someBean;
private SomeBean someBean2;
@Before
public void setUp() throws Exception {
someBean2 = ctx.getBean(SomeBean.class, "2");
}
@Test
public void test() {
System.out.println("test");
}
}
configuration du ressort:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="somepackage"/>
</beans>
La sortie de l'exécution:
Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test
quand j'initialise bean en appelant getBean
de ApplicationContext
tout fonctionne comme prévu. Ma question est pourquoi l'injection de haricot par @Autowire
et @Value
combinaison n'appelle pas @PostConstruct
méthode
5 réponses
pourquoi @Value est-il utilisé au lieu de @Autowired?
l'annotation @Value
est utilisée pour injecter des valeurs et a normalement comme destination des chaînes, des primitives, des types encadrés et des collections java.
selon documentation de ressort :
l'annotation @Value peut être placée sur les champs, les méthodes et les paramètres de méthode/constructeur pour spécifier la valeur par défaut.
Value
reçoit une expression de chaîne de caractères qui est utilisée par le ressort pour manipuler la conversion à l'objet de destination. Cette conversion peut se faire à travers la conversion de type de ressort , le java bean property editor , et le spring's SpEL expressions . L'objet résultant de cette conversion, en principe, n'est pas géré par le printemps (même si vous pouvez retourner un déjà géré par l'une de ces méthodes).
par contre, le Automiredannotationbeanpostprocessor est un
BeanPostProcessor mise en œuvre que autowires annoté des champs, des méthodes de définition et de l'arbitraire config méthodes. Ces éléments à injecter sont détectés par une annotation Java 5: par défaut, les annotations @Autowired et @Value de Spring.
Cette classe gère l'injection de champ, résout les dépendances, et finalement appelle la méthode doResolveDependency , est dans cette méthode où la "priorité" de l'injection est résolue, les ressorts vérifie si une valeur sucrée est présente qui est normalement une chaîne d'expression, cette valeur sucrée est le contenu de l'annotation Value
, donc dans le cas est présent un appel à la classe SimpleTypeConverter est fait, sinon le ressort regarde pour candicate beans and resolues the autowire.
simplement la raison @Autowired
est ignoré et @Value
est utilisé, est parce que la stratégie d'injection de valeur est vérifiée en premier. Évidemment doit toujours être une priorité, le ressort pourrait également jeter une exception lorsque de multiples annotations contradictoires sont utilisées, mais dans ce cas est déterminé par ce contrôle précédent à la valeur sugested.
Je n'ai rien trouvé à propos de cette "priorité" est le printemps, mais simple est parce que n'est pas destiné à utiliser ces annotations ensemble, tout comme par exemple, son pas prévu d'utiliser @Autowired
et @Resource
ensemble soit.
pourquoi @Value crée une nouvelle intance de l'objet
précédemment j'ai dit que la classe SimpleTypeConverter
a été appelé lorsque la valeur suggérée était présente, l'appel spécifique est à la méthode convertifnessary , c'est celui qui effectue la conversion de la chaîne dans l'objet destination, encore une fois cela peut être fait avec un éditeur de propriété ou un convertisseur personnalisé, mais aucun de ceux-ci ne sont utilisés ici. Une expression SpEL n'est pas utilisée non plus, juste une chaîne littérale.
vérifie D'abord si l'objet destination est une chaîne, ou une collection/tableau (peut convertir par exemple la liste délimitée par virgule), puis vérifie si la destination est un enum, si elle l'est, elle essaie de convertir la chaîne, si ce n'est pas, et n'est pas une interface, mais une classe, il vérifie l'existence d'un Constructor(String)
pour finalement créer l'objet (non géré par le printemps). Fondamentalement, ce convertisseur essaie de nombreuses façons différentes de convertir la chaîne à l'objet final.
cette instanciation ne fonctionnera qu'en utilisant une chaîne comme argument, si vous utilisez par exemple , une expression SpEL pour retourner un long @Value("#{2L}")
, et utiliser un objet avec un Constructor(Long)
il lancera un IllegalStateException
avec un message similaire:
ne peut pas convertir la valeur de type 'java.lang.Long' de type 'com.fiberg.test.springboot.objet.Cabane": pas de correspondance des éditeurs ou à la conversion de la stratégie trouvé
Solution Possible
utilisant une classe simple @Configuration comme fournisseur.
public class MyBean {
public MyBean(String myArg) { /* ... */ }
// ...
@PostConstruct public init() { /* ... */ }
}
@Configuration
public class MyBeanSupplier {
@Lazy
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO)
public MyBean getMyBean(String myArg) {
return new MyBean(myArg);
}
}
vous pourriez définir MyBean comme un statique classe dans MyBeanSupplier classe si c'est la seule méthode qu'il aurait. Vous ne pouvez pas non plus utiliser le mode proxy ScopedProxyMode.TARGET_CLASS, parce que vous aurez besoin de fournir les arguments sous forme de fèves et les arguments passés à getMyBean
seront ignorés.
avec cette approche, vous ne seriez pas en mesure d'autowire la fève elle-même, mais à la place, vous seriez autowire le fournisseur et ensuite appeler la méthode get.
// ...
public class SomeBeanTest {
@Autowired private MyBeanSupplier supplier;
// ...
public void setUp() throws Exception {
someBean = supplier.getMyBean("2");
}
}
vous pouvez aussi créer le haricot en utilisant le contexte de l'application.
someBean = ctx.getBean(SomeBean.class, "2");
et la méthode @PostConstruct
doit être appelé peu importe lequel vous utilisez, mais @PreDestroy
n'est pas appelé dans les haricots prototypes .
j'ai lu plusieurs fois les journaux de débogage et la trace de la pile pour les deux scénarios et mes observations sont les suivantes: -
- quand il va créer la fève dans le cas de
@Autowire
, il finit par injecter de la valeur au constructeur via l'utilisation de quelques convertisseurs. Voir la capture d'écran ci-dessous: -
- La @Autowire est inefficace. Donc, dans votre code, si vous supprimez même
@Autowired
ça marchera toujours. Par conséquent, en supportant #1 quand @Value est utilisé sur une propriété, il a essentiellement créé l'objet.
Solution : -
vous devriez avoir un haricot avec le nom arg
injecté avec n'importe quelle valeur que vous voulez. Par exemple: J'ai préféré utiliser la classe de configuration (vous pouvez créer le bean dans le fichier de contexte) et fait ci-dessous: -
@Configuration
public class Configurations {
@Bean
public String arg() {
return "20";
}
}
alors la classe test serait comme ci-dessous (notez que vous pouvez utiliser le changement ContextConfiguration
pour utiliser classpath pour lire le fichier de contexte): -
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SomeBean.class, Configurations.class})
public class SomeBeanTest {
@Autowired
ApplicationContext ctx;
@Autowired
String arg;
@Autowired
private SomeBean someBean;
private SomeBean someBean2;
@Before
public void setUp() throws Exception {
someBean2 = ctx.getBean(SomeBean.class, "2");
}
@Test
public void test() {
System.out.println("\n\n\n\ntest" + someBean.getName());
}
}
donc, un apprentissage pour moi aussi bien d'être prudent avec l'usage de @Value
comme il pourrait être trompeur qu'il a aidé à l'autowiring en injectant de la valeur d'un grain de printemps qui a été créé dans l'arrière-plan et pourrait rendre les applications malfaisantes.
quand vous exécutez le test, un nouveau haricot pour le test est créé (c.-à-d. pas la classe SomeBean, la classe SomeBeanTest). @Value sera instanciée comme une valeur membre (pas une bean) et donc beanpostprocessor par défaut (Autoowiredannotationbeanpostprocessor) ne tentera pas de l'initialiser.
Pour montrer que j'ai déplacé votre Système.hors.println() pour log.info() (garder les lignes de synchronisation). Activer la journalisation au niveau du débogage montre:
DEBUG org.springframework.haricot.usine.annotation.InjectionMetadata - Traitement injecté élément de haricot " somepackage.SomeBeanTest": AutowiredFieldElement for org.springframework.cadre.ApplicationContext somepackage.SomeBeanTest.ctx
DEBUG org.springframework.core.annotation.AnnotationUtils-échec de meta-introspect annotation [interface org.springframework.haricot.usine.annotation.Autocâblés]: java.lang.NullPointerException
DEBUG org.springframework.haricot.usine.annotation.AutowiredAnnotationBeanPostProcessor - permettra à l'autowiring par type de haricot nom " somepackage.SomeBeanTest' bean nommé 'org.springframework.cadre.soutien.Application genericcontext@39c0f4a '
DEBUG org.springframework.haricot.usine.annotation.InjectionMetadata - Traitement injecté élément de haricot " somepackage.SomeBeanTest": AutowiredFieldElement for private somepackage.SomeBean somepackage.SomeBeanTest.someBean
DEBUG org.springframework.core.annotation.AnnotationUtils-échec de meta-introspect annotation [interface org.springframework.haricot.usine.annotation.Valeur]: java.lang.NullPointerException
DEBUG org.springframework.haricot.BeanUtils - Pas de propriété de l'éditeur [somepackage.SomeBeanEditor] trouvé pour le type somepackage.SomeBean selon l'Éditeur de " suffixe convention
INFO somepackage.SomeBean - Constructeur appelé, arg: 0
DEBUG org.springframework.test.cadre.soutien.Abstractdirtirtiescontexttestexecutionlistener-avant la méthode d'essai: ....
INFO somepackage.SomeBeanTest-test
DEBUG org.springframework.test.cadre.soutien.Abstractdirtirtiescontexttestexecutionlistener-après méthode d'essai:
une façon de contourner cela fonctionne est d'initialiser la fève manuellement:
@Value("0")
private SomeBean someBean;
@PostConstruct
private void customInit() {
log.info("Custom Init method called");
someBean.init();
}
qui produira:
INFO somepackage.SomeBean-constructeur appelé, arg: 0
INFO somepackage.SomeBeanTest - méthode Init personnalisée appelée
INFO somepackage.Construction SomeBean-Post appelée
INFO somepackage.SomeBeanTest-test
@Value ne fait pas ce que vous attendez de lui. Il ne peut pas être utilisé pour fournir un arg constructeur au haricot étant créé.
voir ce SO Q & R: Config Java de printemps: comment créer un prototype-scoped @Bean avec des arguments runtime?
si je n'ai pas tort :- Règle de printemps L'injection de champ se produit après que les objets ont été construits car évidemment le conteneur ne peut pas définir une propriété de quelque chose qui n'existe pas. Le champ sera toujours désactivé dans le constructeur.
vous essayez d'Imprimer la valeur injectée (ou de faire une réelle initialisation :)), Utilisation De PostConstruct:- dans votre code, vous avez deux haricots. 1 SomeBean après constructeur appelé la valeur déposée est définie . 2 SomeBean2 vous êtes de passage arg que la valeur 2 qui ont été mis en deuxième haricot vous pouvez utiliser une méthode annotée avec @PostConstruct, qui sera exécuté après le processus d'injection.
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:applicationContext-test.xml")
public class SomeBeanTest {
@Autowired
ApplicationContext ctx;
@Autowired
@Value("1")
private SomeBean someBean;
private SomeBean someBean2;
@Before
public void setUp() throws Exception {
someBean2 = ctx.getBean(SomeBean.class, "2");
}
@Test
public void test() {
System.out.println("test");
}
}