关于java:Spring-天天用bean-懒加载原理你懂吗

10次阅读

共计 4476 个字符,预计需要花费 12 分钟才能阅读完成。

起源:小小木的博客
www.cnblogs.com/wyc1994666/p/10569091.html

一般的 bean 的初始化是在容器启动初始化阶段执行的,而被 lazy-init 润饰的 bean 则是在从容器里第一次进行 context.getBean(“”)时进行触发。

Spring 启动的时候会把所有 bean 信息 (包含 XML 和注解) 解析转化成 Spring 可能辨认的 BeanDefinition 并存到 Hashmap 里供上面的初始化时用。

接下来对每个 BeanDefinition 进行解决,如果是懒加载的则在容器初始化阶段不解决,其余的则在容器初始化阶段进行初始化并依赖注入。

本文我说了很屡次 Spring 容器初始化和 bean 初始化 容器的初始化有可能包含 bean 的初始化次要取决于该 bean 是否是懒加载的,特此说明怕误会。。。:)

一. 先睹为快

话不多说先写个例子看下这属性到底有什么作用,咱们定义了一个叫做 coffee 的一般 bean, 代码如下:

1. 一般非懒加载 bean 的演示

package com.test.spring;

public class Coffee {public Coffee() {System.out.println("正在初始化 bean !!! 调用无参构造函数");
    }

}
<bean name="coffee" class="com.test.spring.Coffee"/>
@Test
public void testLazyInit() {System.out.println("开始初始化 Spring 容器");

    // 非懒加载的 bean 会在容器初始化时进行 bean 的初始化,前面会拿 Spring 启动时的源码进行剖析
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");

   // 非懒加载的 bean 的构造函数会在这个地位打印
   System.out.println("Spring 容器初始化结束");

   System.out.println("开始从容器中获取 Bean");

   Coffee coffee = context.getBean("coffee", Coffee.class);

   System.out.println("获取结束  bean :" + coffee);
}

运行后果如下:

2. 非懒加载 bean 的演示

<bean name="coffee" class="com.test.spring.Coffee" lazy-init="true" />
@Test
public void testLazyInit() {System.out.println("开始初始化 Spring 容器");

    // 在初始化容器阶段不会对懒加载的 bean 进行初始化
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");

    System.out.println("Spring 容器初始化结束");

    System.out.println("开始从容器中获取 Bean");

    // 在这一阶段会对懒加载的 bean 进行初始化
    Coffee coffee = context.getBean("coffee", Coffee.class);

    System.out.println("获取结束  bean :" + coffee);
}

运行后果如下:

二,原理剖析

Spring 启动时次要干俩件事 :

1. 初始化容器 2. 对 bean 进行初始化并依赖注入。(懒加载的 bean 不做第二件)

然而对于大多数 bean 来说,bean 的初始化以及依赖注入就是在容器初始化阶段进行的,只有懒加载的 bean 是当应用程序第一次进行 getBean 时进行初始化并依赖注入。

上面贴出代码看下

Spring 容器初始化代码如下就一行:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        // Spring ioc 启动入口 理解了 refresh 就理解了 ioc
        refresh();}
}

Spring 初始化入口 refresh(省略了局部基本次无关的代码,望了解,太长了影响浏览体验),另外关注公众号 Java 技术栈回复 spring 能够获取系列 Spring 教程。

public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化所有非 懒加载的 bean!!!!finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();}
}

第 20 行则是跟本次主题无关的,就是说在容器启动的时候只解决non-lazy-init bean,懒加载的 bean 在 Spring 启动阶段基本不做任何解决上面看下源码就明确了

点进去第 20 行的 finishBeanFactoryInitialization(beanFactory)外头有个初始化 non-lazy-init bean 的函数 preInstantiateSingletons()

具体逻辑如下

1. 对 beanNames 汇合遍历获取每个 BeanDefinition

2. 判断是否是懒加载的,如果不是则持续解决(non-lazy-init bean 不做解决)

3. 判断是否是 factorybean 如果不是则进行实例化并依赖注入

public void preInstantiateSingletons() throws BeansException {
   // 所有 beanDefinition 汇合
   List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
   // 触发所有非懒加载单例 bean 的初始化
   for (String beanName : beanNames) {
       // 获取 bean 定义
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 判断是否是懒加载单例 bean,如果是单例的并且不是懒加载的则在 Spring 容器
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
          // 判断是否是 FactoryBean
         if (isFactoryBean(beanName)) {final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                      @Override
                      public Boolean run() {return ((SmartFactoryBean<?>) factory).isEagerInit();}
                   }, getAccessControlContext());
                }
         }else {// 如果是一般 bean 则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
             // context.getBean("beanName") 所触发的逻辑是一样的
            getBean(beanName);
         }
      }
   }
}

getBean() 办法是实现 bean 初始化以及依赖注入的函数

@Override
public Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);
}

三,总结

对于被润饰为 lazy-init 的 bean Spring 初始化阶段不会进行 init 并且依赖注入,当第一次进行 getBean 时候进行初始化并依赖注入

对于非懒加载的 bean getBean 的时候会从缓存外头取 因为容器初始化阶段曾经初始化了

// 容器启动初始化 会初始化并依赖注入非懒加载的 bean
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");

// lazy-init bean 会进行第一次初始化并依赖注入  其余的会从缓存里取
Coffee coffee = context.getBean("coffee", Coffee.class);

近期热文举荐:

1.600+ 道 Java 面试题及答案整顿(2021 最新版)

2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0