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

6 浏览
0 Comments

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

我有一个原型作用域的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配置:



    

执行的输出结果:

Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test

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

0
0 Comments

当运行测试时,会创建一个新的测试bean(不是SomeBean类,而是SomeBeanTest类)。将会作为成员值实例化(不是一个bean),因此默认的BeanPostProcessor(AutowiredAnnotationBeanPostProcessor)不会尝试对其进行初始化。

为了证明我已经将System.out.println()移动到log.info()(保持行同步)。启用debug级别的日志记录显示:

DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - 处理bean 'somepackage.SomeBeanTest'的注入元素:AutowiredFieldElement for org.springframework.context.ApplicationContext somepackage.SomeBeanTest.ctx

DEBUG org.springframework.core.annotation.AnnotationUtils - 无法元自省注解 [interface org.springframework.beans.factory.annotation.Autowired]:java.lang.NullPointerException

DEBUG org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - 按类型进行自动装配,从bean名称 'somepackage.SomeBeanTest' 到bean名称 'org.springframework.context.support.GenericApplicationContext'

DEBUG org.springframework.beans.factory.annotation.InjectionMetadata - 处理bean 'somepackage.SomeBeanTest'的注入元素:AutowiredFieldElement for private somepackage.SomeBean somepackage.SomeBeanTest.someBean

DEBUG org.springframework.core.annotation.AnnotationUtils - 无法元自省注解 [interface org.springframework.beans.factory.annotation.Value]:java.lang.NullPointerException

DEBUG org.springframework.beans.BeanUtils - 根据“Editor”后缀约定,找不到类型为somepackage.SomeBean的属性编辑器[somepackage.SomeBeanEditor]

INFO somepackage.SomeBean - 构造方法被调用,arg: 0

DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - 在测试方法之前:....

INFO somepackage.SomeBeanTest - 测试

DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - 在测试方法之后:

解决方法是手动初始化bean:

("0")
private SomeBean someBean;
private void customInit() {
    log.info("Custom Init method called");
    someBean.init();
}

这将产生:

INFO somepackage.SomeBean - 构造方法被调用,arg: 0

INFO somepackage.SomeBeanTest - 自定义初始化方法被调用

INFO somepackage.SomeBean - 后置构造方法被调用

INFO somepackage.SomeBeanTest - 测试

0
0 Comments

@PostConstruct方法在自动装配原型Bean时没有被调用的原因是,当创建Bean时,它通过使用一些转换器将值注入到构造函数中。因此,在代码中即使删除@PostConstruct注解,它仍然会正常工作。解决方法是创建一个名为arg的Bean,并注入所需的值。可以使用配置类或上下文文件创建该Bean。以下是配置类的示例:

public class Configurations {
    public String arg() {
        return "20";
    }
}

然后,在测试类中使用以下代码(注意,可以使用classpath来读取上下文文件):

@SpringJUnit4ClassRunner.class
(classes = {SomeBean.class, Configurations.class})
public class SomeBeanTest {
    ApplicationContext ctx;
    String arg;
    private SomeBean someBean;
    private SomeBean someBean2;
    public void setUp() throws Exception {
        someBean2 = ctx.getBean(SomeBean.class, "2");
    }
    public void test() {
        System.out.println("\n\n\n\ntest" + someBean.getName());
    }
}

因此,我也学到了一个教训,就是在使用@PostConstruct注解时要小心,它可能会误导人认为它通过从后台创建的某个Spring Bean中注入值来帮助自动装配,并导致应用程序出现问题。

0
0 Comments

@PostConstruct方法在自动装配原型bean时没有调用的原因是因为@Autowired注解的优先级高于@Value注解。Spring在处理@Autowired注解时会先检查是否存在@Value注解指定的默认值,如果存在则使用默认值进行注入,否则继续解析依赖并进行自动装配。

解决方法是使用一个简单的供应商类来创建原型bean。可以定义一个供应商类,通过其get方法返回一个新的实例化的bean对象。然后在需要使用该bean的地方,通过注入供应商类对象并调用get方法来获取bean实例。

另外,也可以通过应用程序上下文直接创建bean对象。通过使用应用程序上下文的getBean方法,传入bean的类型和构造函数参数,即可获取到一个新的bean实例。

无论使用哪种方法,@PostConstruct注解的方法都会被调用。但需要注意的是,在原型bean中,@PostConstruct注解的方法不会被调用。

至于为什么会出现@Autowired注解的优先级高于@Value注解的情况,官方文档中并没有明确说明。但可以理解为这两个注解的使用场景不同,@Autowired注解主要用于自动装配bean,而@Value注解主要用于初始化对象时使用配置变量和Spring的SpEL表达式。因此,它们的使用目的和方式不同,导致了注解的优先级不同。

0