我有一个原型范围bean,我想通过@Autowired注释注入它。在这个bean中,还有@PostConstruct方法,Spring没有调用它,我不明白为什么。
我的bean定义:
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");
}
}
我想要注入bean的JUnit类:
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");
}
}
Spring配置:
<?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>
执行的输出:
Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test
当我通过从getBean
调用ApplicationContext
来初始化bean时,一切都按预期工作。我的问题是为什么通过@Autowire
和@Value
组合注入bean不会调用@PostConstruct
方法
答案 0 :(得分:9)
为什么使用@Value代替@Autowired?
@Value
注释用于注入值,通常具有目标字符串,基元,盒装类型和Java集合。
@Value注释可以放在字段,方法和方法/构造函数参数上,以指定默认值。
Value
接收一个字符串表达式,spring用它来处理到目标对象的转换。此转换可以通过Spring's type conversion,java bean property editor和Spring's SpEL expresions进行。原则上,此转换的结果对象不受spring管理(即使您可以从任何此方法返回已管理的bean)。
另一方面,AutowiredAnnotationBeanPostProcessor是
BeanPostProcessor实现,自动装配带注释的字段,setter方法和任意配置方法。要注入的成员是通过Java 5注释检测的:默认情况下,Spring的@Autowired和@Value注释。
这个类处理字段注入,解析依赖关系,并最终调用方法doResolveDependency,这个方法中的优先级是&#39;注入解析后,spring检查是否存在通常为表达式字符串的sugested值,此sugested值是注释Value
的内容,因此如果存在对类{{3}的调用制造,否则春天会寻找甜食豆并解决汽车的问题。
简单地忽略@Autowired
的原因并使用@Value
是因为首先检查值的注入策略。显然总是必须优先考虑,当使用多个冲突注释时,spring也会抛出异常,但在这种情况下,由之前检查的sugested值决定。
我无法找到与此优先级相关的任何内容。是春天,但很简单是因为不打算一起使用这个注释,例如,它不打算同时使用@Autowired
和@Resource
。
为什么@Value会创建对象的新内容
之前我说当建议的值存在时调用了类SimpleTypeConverter
,特定的调用是方法SimpleTypeConverter,这是执行将字符串转换为目标的方法对象,这可以使用属性编辑器或自定义转换器完成,但这里没有使用这些。 SpEL表达式也没有使用,只是一个字符串文字。
Spring首先检查目标对象是字符串,还是集合/数组(可以转换为逗号分隔列表),然后检查目标是否为枚举,如果是,则尝试转换字符串,如果是不是,并且不是接口而是类,它检查Constructor(String)
的存在以最终创建对象(不由spring管理)。基本上,这个转换器尝试了许多不同的方法将字符串转换为最终对象。
此实例化仅使用字符串作为参数,如果您使用例如SpEL表达式返回长@Value("#{2L}")
,并使用带有Constructor(Long)
的对象,它将抛出{{ 1}}有类似的消息:
无法转换类型&#39; java.lang.Long&#39;的值到必需的类型&#39; com.fiberg.test.springboot.object.Hut&#39;:找不到匹配的编辑器或转换策略
可能的解决方案
使用简单的@Configuration类作为供应商。
IllegalStateException
如果MyBean是唯一的方法,可以将MyBean定义为MyBeanSupplier类中的静态类。此外,您不能使用代理模式ScopedProxyMode.TARGET_CLASS,因为您需要将参数作为bean提供,并且传递给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);
}
}
的参数将被忽略。
使用这种方法,您将无法自动装配bean本身,相反,您可以自动装配供应商,然后调用get方法。
getMyBean
您还可以使用应用程序上下文创建bean。
// ...
public class SomeBeanTest {
@Autowired private MyBeanSupplier supplier;
// ...
public void setUp() throws Exception {
someBean = supplier.getMyBean("2");
}
}
无论您使用哪种方法,都应调用someBean = ctx.getBean(SomeBean.class, "2");
方法,但@PostConstruct
convertIfNecessary。
答案 1 :(得分:2)
我多次阅读这两种情况的调试日志和堆栈跟踪,我的观察结果如下: -
@Autowire
的情况下创建bean时,它基本上最终通过使用一些转换器向构造函数注入值。见下面的截图: - @Autowired
,它仍然有效。因此,当在属性上使用@Value时支持#1它基本上创建了Object。解决方案: -
你应该让一个名为arg
的bean注入你想要的任何值。例如。我更喜欢使用配置类(您可以在上下文文件中创建bean)并在下面执行: -
@Configuration
public class Configurations {
@Bean
public String arg() {
return "20";
}
}
然后测试类如下(注意你可以使用更改ContextConfiguration
来使用类路径来读取上下文文件): -
@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());
}
}
所以,对我来说也要学习@Value
的使用,因为它可能会误导它通过从后台创建的一些spring bean中注入值来帮助自动装配,并且可以创建应用程序出现异常。
答案 2 :(得分:0)
运行测试时,会创建一个用于测试的新bean(即不是SomeBean类,SomeBeanTest类)。 @Value将被实例化为成员值(不是bean),因此默认的BeanPostProcessor(AutowiredAnnotationBeanPostProcessor)不会尝试初始化它。
显示我已将System.out.println()移动到log.info()(保持行同步)。启用调试级别日志记录显示:
DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - 处理bean的注入元素'somepackage.SomeBeanTest':org.springframework.context.ApplicationContext的autowiredFieldElement somepackage.SomeBeanTest.ctx
DEBUG org.springframework.core.annotation.AnnotationUtils - 无法进行meta-introspect注释[interface org.springframework.beans.factory.annotation.Autowired]:java.lang.NullPointerException
DEBUG org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - 从bean名称'somepackage.SomeBeanTest'按类型自动装配到名为'org.springframework.context.support.GenericApplicationContext@39c0f4a'的bean
DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - 处理bean的注入元素'somepackage.SomeBeanTest':私有somepackage的AutowiredFieldElement.SomeBean somepackage.SomeBeanTest.someBean
DEBUG org.springframework.core.annotation.AnnotationUtils - 无法对meta-introspect注释[interface org.springframework.beans.factory.annotation.Value]:java.lang.NullPointerException
DEBUG org.springframework.beans.BeanUtils - 根据'编辑'后缀约定找到类型somepackage.SomeBean的属性编辑器[somepackage.SomeBeanEditor]
INFO somepackage.SomeBean - 名为arg:0的构造函数
DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - 在测试方法之前:....
INFO somepackage.SomeBeanTest - test
DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - 测试方法后:
一种可行的解决方法是手动初始化bean:
@Value("0")
private SomeBean someBean;
@PostConstruct
private void customInit() {
log.info("Custom Init method called");
someBean.init();
}
将产生:
INFO somepackage.SomeBean - 名为arg:0的构造函数
INFO somepackage.SomeBeanTest - 名为
的自定义Init方法INFO somepackage.SomeBean - 名为
的帖子结构INFO somepackage.SomeBeanTest - test
答案 3 :(得分:-1)
@Value没有做你期望它做的事。它不能用于向正在创建的bean提供构造函数arg。
见SO Q&amp; A:Spring Java Config: how do you create a prototype-scoped @Bean with runtime arguments?
答案 4 :(得分:-1)
如果我没错: - Spring RULE 在构造对象之后发生字段注入,因为很明显容器不能设置不存在的东西的属性。该字段将始终在构造函数中取消设置。
您正在尝试打印注入的值(或进行一些真正的初始化:)), 使用PostConstruct: - 在你的代码中你有两个bean。 设置为调用字段值的构造函数后,设置 1 SomeBean 。 2 SomeBean2您传递的arg为值2,已在第二个bean中设置 您可以使用@PostConstruct注释的方法,该方法将在注入过程之后执行。
Document document = builder.parse(new InputSource(new StringReader(dataString)));
NodeList newDataSetList = document.getElementsByTagName("NewDataSet");
readChildNodes(NodeList newDataSetList)
public void readChildNodes(NodeList newDataSetList){
for(int i=0;i<newDataSetList.getLength(); i++) {
Node tableNode = newDataSetList.item(i);
NodeList tableDataList = tableNode.getChildNodes();
readChildNodes(tableDataList);
System.out.println(tableNode.getNodeName());
}
}