为什么在使用构造函数参数自动连接原型Bean时不调用@PostConstruct方法

我有一个原型范围的bean,我想通过@Autowired注释来注入它。在这个豆子中,还有@PostConstruct方法,不是Spring调用的,我不明白为什么。

我的豆子定义:

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

弹簧配置:

<?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

当我通过调用来初始化Bean时,一切都按预期工作。我的问题是为什么注入bean by和组合不是调用方法getBeanApplicationContext@Autowire@Value@PostConstruct


答案 1

为什么使用@Value而不是@Autowired?

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

根据Spring的文档

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

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

另一方面,AutowiredAnnotationBeanPostProcessor是一个

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

这个类处理字段注入,解析依赖关系,并最终调用方法doResolveDependency,在此方法中,注入的“优先级”被解析,弹簧检查是否存在sugested值,这通常是一个表达式字符串,这个sugested值是注释的内容,所以在存在的情况下,对类SimpleTypeConverter的调用是, 否则,spring会寻找可指示的豆子并解析自动连线。Value

简单地说,原因被忽略并被使用,是因为首先检查了价值的注入策略。显然,必须始终是优先级,当使用多个冲突的注释时,spring也可能引发异常,但在这种情况下,由先前对 sugested 值的检查确定。@Autowired@Value

我找不到任何与此“优先级”相关的内容,但简单是因为不打算一起使用此注释,就像例如,它不打算一起使用和一起使用一样。@Autowired@Resource


为什么@Value会创建对象的新插入

之前我说过,当存在建议的值时调用了该类,具体的调用是对方法convertIf必要性,这是执行将字符串转换为目标对象的类,同样,这可以通过属性编辑器或自定义转换器来完成,但这里没有使用其中任何一个。SpEL 表达式也不使用,只是一个字符串文本。SimpleTypeConverter

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

此实例化只能使用字符串作为参数,例如,如果您使用SpEL表达式来返回长,并且使用带有它的对象将抛出具有类似消息的对象:@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);
    }
}

您可以将 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");
    }
}

您还可以使用应用程序上下文创建 Bean。

someBean = ctx.getBean(SomeBean.class, "2");

无论你使用哪一个,都应该调用该方法,但不在原型bean中调用@PostConstruct@PreDestroy


答案 2

我多次通读了这两种方案的调试日志和堆栈跟踪,我的观察结果如下:

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

converters are used

  1. @Autowire无效。因此,在您的代码中,即使删除它,它仍然有效。因此,当在属性上使用@Value时,支持#1基本上创建了对象。@Autowired

解决方案:-

你应该有一个豆子,他的名字注入了你想要的任何值。例如,我更喜欢使用配置类(您可以在上下文文件中创建bean),并在下面做了:-arg

@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


推荐