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

ger*_*ard 20 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");
    }

}
Run Code Online (Sandbox Code Playgroud)

我想要注入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");
    }
}
Run Code Online (Sandbox Code Playgroud)

弹簧配置:

<?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>
Run Code Online (Sandbox Code Playgroud)

执行输出:

Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test
Run Code Online (Sandbox Code Playgroud)

当我通过调用初始化豆getBeanApplicationContext一切正常.我的问题是为什么注入bean @Autowire@Value组合不是调用@PostConstruct方法

Jos*_*lva 9

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

@Value注释用于注入的值,并且通常具有与目标串,原语,盒装类型和Java集合.

根据Spring的文档:

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

Value接收一个字符串表达式,由spring用来处理到目标对象的转换.这种转换可以通过Spring的类型转换,java bean属性编辑器Spring的SpEL表达式进行.原则上,此转换的结果对象不受spring管理(即使您可以从任何此方法返回已管理的bean).

另一方面,AutowiredAnnotationBeanPostProcessor是一个

BeanPostProcessor实现,自动装配带注释的字段,setter方法和任意配置方法.要注入的这些成员是通过Java 5注释检测的:默认情况下,Spring的@Autowired和@Value注释.

这个类处理字段注入,解析依赖关系,并最终调用方法doResolveDependency,在这个方法中解决了注入的'priority',spring检查是否存在一个通常是表达式字符串的sugested值,这个sugested value是注释的内容Value,因此如果存在,则调用类SimpleTypeConverter,否则spring会查找candicate bean并解析autowire.

简单地@Autowired忽略并@Value使用原因,是因为首先检查值的注入策略.显然总是必须优先考虑,当使用多个冲突注释时,spring也会抛出异常,但在这种情况下,由之前检查的sugested值决定.

我无法找到与此相关的"优先"是春天什么,但简单的是因为不打算使用这个注解在一起,就如同例如,它不打算使用@Autowired@Resource两种在一起.


为什么@Value会创建对象的新内容

以前我说SimpleTypeConverter当建议值存在时调用类,具体调用是convertIfNecessary方法,这是执行将字符串转换为目标对象的方法,同样可以使用属性编辑器或者自定义转换器,但这里没有使用它们.也不使用SpEL表达式,只是字符串文字.

Spring首先检查目标对象是字符串,还是集合/数组(可以转换为逗号分隔列表),然后检查目标是否为枚举,如果是,则尝试转换字符串,如果不是,则它不是一个接口而是一个类,它检查a的存在Constructor(String)以最终创建对象(不由spring管理).基本上,此转换器尝试许多不同的方式将字符串转换为最终对象.

这个实例化只能使用字符串作为参数,如果您使用例如SpEL表达式返回long @Value("#{2L}"),并且使用带有Constructor(Long)它的对象将IllegalStateException使用类似的消息抛出:

无法将'java.lang.Long'类型的值转换为必需类型'com.fiberg.test.springboot.object.Hut':找不到匹配的编辑器或转换策略


可能解决方案

使用简单的@Configuration类作为供应商.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将MyBean定义为MyBeanSupplier类中的静态类,如果它是唯一的方法.此外,您不能使用代理模式ScopedProxyMode.TARGET_CLASS,因为您需要提供bean作为参数,getMyBean并且将忽略传递给的参数.

使用这种方法,您将无法自动装配bean本身,而是通过自动装配供应商然后调用get方法.

// ...
public class SomeBeanTest {
    @Autowired private MyBeanSupplier supplier;
    // ...
    public void setUp() throws Exception {
        someBean = supplier.getMyBean("2");
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用应用程序上下文创建bean.

someBean = ctx.getBean(SomeBean.class, "2");
Run Code Online (Sandbox Code Playgroud)

@PostConstruct无论您使用哪种方法,都应调用该方法,但@PreDestroy 不会在原型bean中调用该方法.


Jam*_*tti -1

@Value 没有做你期望它做的事情。它不能用于向正在创建的 bean 提供构造函数参数。

请参阅此 SO Q&A:Spring Java Config:如何使用运行时参数创建原型范围的 @Bean?