lazy-init:bean延迟初始化

2022/01/12 spring专题 共 7122 字,约 21 分钟
闷骚的程序员

1. spring对bean的实例化一般有两种

1.实时初始化bean
2.延迟初始化bean

注意,这里的“延迟初始化”中的“初始化”实际上是习惯性叫法,正确理解应该是“延迟实例化”。

2. bean实时初始化

在容器启动过程中被创建组装好的bean,称为实时初始化的bean。
spring中默认定义的bean都是实时初始化的bean,这些bean默认都是单例的,在容器启动过程中会被创建好,然后放在spring容器中以供使用。

实时初始化bean的有一些优点:
1.更早发下bean定义的错误:实时初始化的bean如果定义有问题,会在容器启动过程中会抛出异常,让开发者快速发现问题。
2.查找bean更快:容器启动完毕之后,实时初始化的bean已经完全创建好了,此时被缓存在spring容器中,当我们需要使用的时候,容器直接返回就可以了,速度是非常快的。

3. 案例-默认单例bean的实时初始化

zeh.myspring.spring1xml.code10lazyinit.ActualTimeBean

package zeh.myspring.spring1xml.code10lazyinit;

// 实时初始化的单例bean
public class ActualTimeBean {

    // 一会我们在spring中创建这个对象,构造函数中会输出一段话,这段话会在spring容器创建过程中输出。
    public ActualTimeBean() {
        System.out.println("我是实时初始化的bean!");
    }
}

这个类在构造器中输出了一句话,用于我们后续的验证。
如果在spring容器启动后这句话就输出了,说明在spring容器启动过程中这个bean就已经被创建了。
否则,说明没有创建。

定义spring配置文件:
springxml/code10-applicationContext-actual-time.xml

<?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-3.1.xsd
		http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<!-- 实时初始化的单例bean -->
	<bean id="actualTimeBean" class="zeh.myspring.spring1xml.code10lazyinit.ActualTimeBean"/>

</beans>

测试:
zeh.myspring.spring1xml.code10lazyinit.RunLazyInitJunit

package zeh.myspring.spring1xml.code10lazyinit;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import zeh.myspring.spring1xml.code00common.LoadSpringConfigFactory;

public class RunLazyInitJunit {

    // 测试实时初始化的bean。
    @Test
    public void testActualTimeBean() {
        // 从结果可以看到:默认情况下,bean是单例的,是实时初始化的,在spring容器启动过程中就预加载了bean。
        System.out.println("spring容器启动中...");
        // 启动spring容器
        LoadSpringConfigFactory.getApplicationContext("springxml/code10-applicationContext-actual-time.xml");
        System.out.println("spring容器启动完毕...");
    }
}

输出日志:

spring容器启动中...
我是实时初始化的bean!
spring容器启动完毕...

从日志可以看出,容器启动前后,有“我是实时初始化的bean!”这句日志输出,说明actualTimeBean这个bean是在容器启动过程中就被创建好的。

注意:

bean默认是实时初始化的,可以通过bean元素的lazy-init="true"将bean定义为延迟初始化。
bean默认是单例的,只有单例bean才有实时初始化和延迟初始化之分,多例bean都是等客户端去容器中查找的时候才进行延迟初始化的,要不然怎么能多例呢。
也就是说多例bean默认都是延迟初始化的,对多例bean设置lazy-init="true"或者lazy-init="false"毫无意义。    

4. 案例-延迟初始化的bean

实时初始化的bean都会在容器启动过程中创建好。
如果程序中定义的bean非常多,并且有些bean创建的过程中比较耗时的时候,会导致系统消耗的资源比较多,并且会让整个启动时间比较长。
这个我估计大家都是有感受的,使用spring开发的系统比较大的时候,整个系统启动耗时是比较长的,基本上多数时间都是在创建和组装bean。
spring对这些问题也提供了解决方案: bean延迟初始化。

所谓延迟初始化,就是和实时初始化刚好相反,延迟初始化的bean在容器启动过程中不会创建,而是需要使用的时候才会去创建,先说一下bean什么时候会被使用:
(1)被其他bean作为依赖进行注入的时候,比如通过property元素的ref属性进行引用,通过构造器注入、通过set注入、通过自动注入,这些都会导致被依赖bean的创建。
(2)开发者自己写代码向容器中查找bean的时候,如调用容器的getBean方法获取bean。
上面这2种情况会导致延迟初始化的bean被创建。

延迟初始化bean的spring配置: 在bean定义的时候通过lazy-init属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化:

<bean lazy-init="是否是延迟初始化" />

4.1 案例1

zeh.myspring.spring1xml.code10lazyinit.LazyInitBean:

package zeh.myspring.spring1xml.code10lazyinit;

// 延迟初始化的单例bean
public class LazyInitBean {
    public LazyInitBean() {
        System.out.println("我是延迟初始化的bean!");
    }
}

spring配置:
springxml/code10-applicationContext-lazy-init.xml

<?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-3.1.xsd
		http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd">


	<!-- 延迟初始化的单例bean -->
	<!-- 在bean定义的时候通过lazy-init属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化-->
	<bean id="lazyInitBean" class="zeh.myspring.spring1xml.code10lazyinit.LazyInitBean" lazy-init="true"/>
</beans>

注意上面的lazy-init=”true”表示定义的这个bean是延迟初始化的bean。

测试:在上面的测试类中加一个方法:

    @Test
    public void testLazyInitBean() {
        System.out.println("spring容器启动中...");
        ApplicationContext context = LoadSpringConfigFactory.getApplicationContext("springxml/code10-applicationContext-lazy-init.xml");
        System.out.println("spring容器启动完毕...");
        System.out.println("从容器中开始查找LazyInitBean");
        // 代码结合输出可以看出来,LazyInitBean在容器启动过程中并没有创建,当我们调用context.getBean方法的时候,LazyInitBean才被创建的。
        LazyInitBean lazyInitBean = context.getBean(LazyInitBean.class);
        System.out.println("LazyInitBean:" + lazyInitBean);
    }

日志:

spring容器启动中...
spring容器启动完毕...
从容器中开始查找LazyInitBean
我是延迟初始化的bean!
LazyInitBean:zeh.myspring.spring1xml.code10lazyinit.LazyInitBean@2b552920

从结果可以看出:
LazyInitBean在容器启动过程中并没有创建,当我们调用context.getBean方法的时候,LazyInitBean才被主动创建的。

4.2 案例2

上面这种方式是我们主动从容器中获取bean的时候,延迟初始化的bean才被容器创建的。
下面我们再来看一下当延迟初始化的bean被其他实时初始化的bean依赖的时候,是什么时候创建的。

zeh.myspring.spring1xml.code10lazyinit.ActualTimeDependencyLazyBean

package zeh.myspring.spring1xml.code10lazyinit;

// 实时初始化的bean依赖延迟初始化的bean
public class ActualTimeDependencyLazyBean {

    public ActualTimeDependencyLazyBean() {
        System.out.println("ActualTimeDependencyLazyBean实例化!");
    }

    private LazyInitBean lazyInitBean;

    public LazyInitBean getLazyInitBean() {
        return lazyInitBean;
    }

    public void setLazyInitBean(LazyInitBean lazyInitBean) {
        this.lazyInitBean = lazyInitBean;
        System.out.println("ActualTimeDependencyLazyBean.setLazyInitBean方法!");
    }
}

spring配置:
springxml/code10-applicationContext-actual-depend-lazy.xml

<?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-3.1.xsd
		http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd">


	<!-- 延迟初始化的单例bean -->
	<!-- 在bean定义的时候通过lazy-init属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化-->
	<bean id="lazyInitBean" class="zeh.myspring.spring1xml.code10lazyinit.LazyInitBean" lazy-init="true"/>
	<!-- 实时初始化的单例bean依赖延迟初始化的单例bean-->
	<!-- actualTimeDependencyLazyBean:通过property元素来注入lazyInitBean。-->
	<!-- actualTimeDependencyLazyBean中没有指定lazy-init,默认为false,表示是实时创建的bean,会在容器创建过程中被初始化。-->
	<bean id="actualTimeDependencyLazyBean" class="zeh.myspring.spring1xml.code10lazyinit.ActualTimeDependencyLazyBean">
		<property name="lazyInitBean" ref="lazyInitBean"/>
	</bean>

</beans>

测试:

    // 上面的方式是我们主动从容器中获取bean的时候,延迟初始化的bean才被容器创建的。
    // 下面我们再来看一下当延迟初始化的bean被其他实时初始化的bean依赖的时候,是什么时候创建的。
    @Test
    public void testActualTimeDependencyLazyBean(){
        System.out.println("spring容器启动中...");
        // 启动spring容器
        LoadSpringConfigFactory.getApplicationContext("springxml/code10-applicationContext-actual-depend-lazy.xml");
        System.out.println("spring容器启动完毕...");
    }

日志:

spring容器启动中...
ActualTimeDependencyLazyBean实例化!
我是延迟初始化的bean!
ActualTimeDependencyLazyBean.setLazyInitBean方法!
spring容器启动完毕...

从容器中可以看到,xml中定义的2个bean都在容器启动过程中被创建好了。
有些朋友比较迷茫,lazyInitBean的lazy-init为true,怎么也在容器启动过程中被创建呢?
由于actualTimeDependencyLazyBean为实时初始化的bean,而这个bean在创建过程中需要用到lazyInitBean。
此时容器会去查找lazyInitBean这个bean,然后会进行初始化,所以这2个bean都在容器启动过程中被创建的。

5. 总结

延迟初始化的bean无法在程序启动过程中迅速发现bean定义的问题,第一次获取的时候可能耗时会比较长。在实际工作中用的比较少,作为了解,以后碰到的时候会有个印象。
实时初始化和延迟初始化只针对单例bean。多例bean设置延迟配置无效,默认只能是延迟初始化的。

文档信息

Search

    Table of Contents