当使用构造函数参数自动装配原型bean时,为什么不调用@PostConstruct方法

时间:2018-02-27 11:25:36

标签: java spring postconstruct prototype-scope

我有一个原型范围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方法

5 个答案:

答案 0 :(得分:9)

为什么使用@Value代替@Autowired?

@Value注释用于注入值,通常具有目标字符串,基元,盒装类型和Java集合。

根据Spring's documentation

  

@Value注释可以放在字段,方法和方法/构造函数参数上,以指定默认值。

Value接收一个字符串表达式,spring用它来处理到目标对象的转换。此转换可以通过Spring's type conversionjava bean property editorSpring'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)

我多次阅读这两种情况的调试日志和堆栈跟踪,我的观察结果如下: -

  1. 当它在@Autowire的情况下创建bean时,它基本上最终通过使用一些转换器向构造函数注入值。见下面的截图: -
  2. converters are used

    1. @Autowire无效。因此,在您的代码中,如果您删除@Autowired,它仍然有效。因此,当在属性上使用@Value时支持#1它基本上创建了Object。
    2. 解决方案: -

      你应该让一个名为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());
}
}