Spring延迟加载 - 加载一个bean加载该类的所有@Lazy bean

时间:2014-04-17 05:38:24

标签: java spring spring-bean

我已经声明了两个相同类型的bean。将它们初始化为@Lazy@Autowiring它们中的一个bean也自动初始化了另一个bean。我很惊讶地看到这种行为。只是想知道更多有关机制的信息。

代码

//bean
public class HelloWorld {
    public HelloWorld(String msg){
         System.out.println( msg + ", " + this);
    }   
}

@Configuration
@Lazy
public class SpringAppContext {

     @Bean(name="helloworld1")
     public HelloWorld helloworld1(){
        return new HelloWorld("helloworld1");
     }  
     @Bean(name="helloworld2")
     public HelloWorld helloworld2(){
        return new HelloWorld("helloworld2");
     }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringAppContext.class})
public class SpringBeanLazyLoadTest {
     @Autowired
     private HelloWorld helloworld2;

     @Test // this test is lame but just trying out.
     public void print(){
        System.out.println("Autowired: " + helloworld2);
     }
}

输出

helloworld2, my.entp.spring.HelloWorld@3a9bba
helloworld1, my.entp.spring.HelloWorld@163f7a1 // why was helloworld1 initialized?
Autowired: my.entp.spring.HelloWorld@3a9bba

如果观察输出,您可能会注意到helloworld1 helloworld2 @Autowired@Autowired bean已初始化。

我通过删除{{1}}进行了测试,并产生了预期结果:没有初始化bean。

4 个答案:

答案 0 :(得分:9)

直接使用@Autowired时,注入方法为byType。换句话说,容器看到了

 @Autowired
 private HelloWorld helloworld2;

并尝试在HelloWorld中找到要注入的ApplicationContext类型的bean。

解析要注入的bean的过程包括获取包含创建bean的所有候选bean。因此,@Lazy的bean不会改变任何东西。它们仍然必须被创建才能被注入。

为了澄清M. Deinum's对此问题的评论,您已经提供了豆类名称。例如,

 @Bean(name="helloworld1")

当注射过程发生时,Spring会找到所有可注射的候选bean。如果有多个,它将​​过滤掉它们并尝试找到最佳候选者。如果它不能,它将抛出异常。找到更好的候选者的步骤之一是将bean名称与目标字段的名称进行比较。由于您的匹配,将选择名为helloworld2的bean。

删除@Autowired后,永远不会从ApplicationContext请求bean,因此永远不会初始化。

答案 1 :(得分:2)

从春天docs

  

但是,当懒惰初始化的bean是单例的依赖项时   不是延迟初始化的bean,ApplicationContext创建   启动时懒惰初始化的bean,因为它必须满足   单身人士的依赖。懒惰初始化的bean被注入到   其他地方没有懒惰初始化的单例bean。

在你的测试用例中,lazy bean被急切地初始化,因为Spring测试工具的默认行为是完全准备测试类实例(通过急切地注入所有依赖项)然后将其交给JUnit。它发生的确切位置是DependencyInjectionTestExecutionListener

protected void injectDependencies(final TestContext testContext) throws Exception {
        Object bean = testContext.getTestInstance();
        AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
        beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
        beanFactory.initializeBean(bean, testContext.getTestClass().getName());
        testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
    }

答案 2 :(得分:1)

老帖但仍然。现在你可以这样做:

 @Bean(name="helloworld1", autowire=Autowire.BY_NAME)
 public HelloWorld helloworld1(){
    return new HelloWorld("helloworld1");
 }  
 @Bean(name="helloworld2", autowire=Autowire.BY_NAME)
 public HelloWorld helloworld2(){
    return new HelloWorld("helloworld2");
 }

和/或 @Qualifier

 @Autowired
 @Qualifier("helloworld2")
 private HelloWorld hello;

答案 3 :(得分:0)

如果helloworld1 bean也被初始化,那似乎是Spring的错误。 @Lazy注释将在决定何时初始化Spring bean的过程中发挥作用,而与从何处调用它无关(测试或其他方式)。

只需使用Spring 5.1.0进行尝试,它就可以正确地仅初始化helloworld2 bean。

如果碰巧删除了@Lazy批注,则helloworld1helloworld2 bean都将被初始化。 这是由于Spring将在refresh阶段中实例化所有渴望的bean。一旦在invokeBeanFactoryPostProcessors阶段发现了所有Spring Bean,Spring就会为preInstantiateSingletons中的所有bean调用beanFactory,这将初始化所有渴望的bean(如果需要的话,还有一些Lazy bean作为热切的bean初始化的一部分)。

如果您的情况是这样,由于顶级bean SpringAppContext是惰性的,因此这同样适用于HelloWorld类型的bean。

如果删除@Autowired,则不会急切创建bean,也不需要进行自动装配,因此无需初始化。