springboot Autowired 延迟加载_System

懒加载的几个疑问??



  • 对于使用了@Component注解的类如何统一做延迟加载,是否可以通过Bean定义的后置处理器BeanDefinitionRegistryPostProcessor做统一设置?

答案是做不到,BeanDefinitionRegistryPostProcessor并未提供便利bean定义的方法,即使有也不好控制那些bean设置成懒加载

  • BeanB依赖BeanA,当BeanB还未使用的时候BeanA是否会被加载?

答案是BeanA会被加载,因为BeanB在设置属性的时候需要先加载BeanA。所以即使BeanB还未被使用BeanA也会被加载。

/** * @Author: ubuntuvim * @Date: 2020/7/17 20:46 */@Component@Lazypublic class BeanOne {    public BeanOne() {        System.out.println("beanFactory开始加载BeanOne");    }    public void sayHelloBeanOne() {        System.out.println("调用BeanOne的方法");    }}
/** * @Author: ubuntuvim * @Date: 2020/7/17 20:47 */@Component@Lazypublic class BeanTwo {    public BeanTwo() {        System.out.printf("开始加载BeanTwo");    }    @Resource    BeanOne beanOne;    public void invokeBeanOneMethod() {        System.out.println("在BeanTwo里面调用BeanOne的方法");        beanOne.sayHelloBeanOne();    }}
/** * 指定扫描的包路径 * @Author: ubuntuvim * @Date: 2020/7/17 20:54 */@Configuration@ComponentScan("com.ubuntuvim.spring.lazyloading")public class BeanLoadingConfig {}

验证如下。

package com.ubuntuvim.spring.lazyloading;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;/** * @Author: ubuntuvim * @Date: 2020/7/17 20:53 */public class LazyLoadingTest {    public static void main(String[] args) {        ApplicationContext ac = new AnnotationConfigApplicationContext(BeanLoadingConfig.class);        BeanTwo beanTwo = (BeanTwo) ac.getBean("beanTwo");        // 未调beanTwo 的方法之后,不应该加载BeanOne。        System.out.println("未调beanTwo 的方法之后,不应该加载BeanOne(但事与愿违)");        beanTwo.invokeBeanOneMethod();        /*        执行结果:            开始加载BeanTwo            beanFactory开始加载BeanOne  ---- 因为BeanTwo里面引用了BeanOne,所以BeanOne在BeanTwo加载的时候也被加载了,不然无法初始化属性。            未调beanTwo 的方法之后,不应该加载BeanOne            在BeanTwo里面调用BeanOne的方法         */        // 只使用BeanOne时,BeanTwo不会被加载        BeanOne beanOne = (BeanOne) ac.getBean("beanOne");        /*        执行结果:            beanFactory开始加载BeanOne         */    }}
  • 对于实现了Spring初始化方法的bean,设置lazy-init属性为true是否能起到延迟加载的效果??

答案是:实现了InitializingBean的bean本身使用了@Lazy注解是可以延迟加载的。但是如果是父类实现了InitializingBean的方法,但是没有使用@Lazy注解,而是在子类中使用了@Lazy注解,那么父类的spring初始化方法是会被执行的,@Lazy注解只是对当前类起效果,并不能控制父类的懒加载。

/** * @Author: ubuntuvim * @Date: 2020/7/17 21:26 */@Component@Lazypublic class MyInitializingBean implements InitializingBean {    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("实现了InitializingBean的afterPropertiesSet()方法被调用了");    }}
/** * 未使用@Lazy注解,但是子类使用@Lazy注解,是否可以实现延迟加载呢?答案是不行,@Lazy只对当前类起效果。 * @Author: ubuntuvim * @Date: 2020/7/17 21:26 */@Componentpublic class MyInitializingBeanNoLazy implements InitializingBean {    public MyInitializingBeanNoLazy() {        System.out.println("MyInitializingBeanNoLazy被加载了");    }    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====");        // 即使子类使用了@Lazy注解父类的afterPropertiesSet方法也会被调用的。    }}/** * 本类使用了延迟加载注解,并不能对父类的加载起作用 * @Author: ubuntuvim * @Date: 2020/7/17 21:35 */@Component@Lazypublic class MyInitializingBeanSubClass extends MyInitializingBeanNoLazy {    public MyInitializingBeanSubClass() {        System.out.println("MyInitializingBeanNoLazy的子类使用@Lazy注解,子类被加载了");    }}

执行效果,MyInitializingBeanNoLazy一开始就被加载了。

MyInitializingBeanNoLazy被加载了===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====开始加载BeanTwo未调beanTwo 的方法之后,不应该加载BeanOne(但事与愿违)在BeanTwo里面调用BeanOne的方法beanFactory开始加载BeanOne调用BeanOne的方法在未使用MyInitializingBean之前,如果bean使用了@Lazy注解,即使这个bean实现了InitializingBean的接口也不会被加载实现了InitializingBean的afterPropertiesSet()方法被调用了BUILD SUCCESSFUL in 9s80 actionable tasks: 2 executed, 78 up-to-date21:42:01: Task execution finished 'LazyLoadingTest.main()'.
  • 使用XML方式定义使用Spring初始化方法的类是否可以实现懒加载??

定义一个实现了Spring初始化方法的Bean。

/** * 未使用@Lazy注解,但是子类使用@Lazy注解,是否可以实现延迟加载呢?答案是不行,@Lazy只对当前类起效果。 *

* xml方式定义bean可以实现懒加载,因为不是继承方式,这种方式就是别名的方式,MyInitializingBeanSubClassUseLazy instanceOf MyInitializingBeanNoLazy => true * * * *




* 但是,当另外一个类BeanA里面引用了myInitializingBeanSubClassUseLazy,也不能实现懒加载,因为BeanA在设置属性的时候会先初始化myInitializingBeanSubClassUseLazy。 * * @Author: ubuntuvim * @Date: 2020/7/17 21:26 */@Componentpublic class MyInitializingBeanNoLazy implements InitializingBean { public MyInitializingBeanNoLazy() { System.out.println(this.getClass().getName() + "被加载了"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 ====="); // 即使子类使用了@Lazy注解父类的afterPropertiesSet方法也会被调用的。 System.out.println("name的值是:" + + ""); } /** * 通过xml注入 */ String name; public String getName() { return name; } public void setName(String name) { = name; }}




<?xml version="1.0" encoding="UTF-8"?>

验证结果

package com.ubuntuvim.spring.lazyloading;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/** * @Author: ubuntuvim * @Date: 2020/7/17 22:03 */public class LazyLoadingXmlContextTest {    public static void main(String[] args) {        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath*:lazy-loading-test.xml");        // 容器加载,MyInitializingBeanSubClassUseLazy和MyInitializingBeanSubClassNotUseLazy都交给容器管理。        /*        执行结果可见,afterPropertiesSet()方法被调用了。        MyInitializingBeanNoLazy被加载了,name =        ===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====        name的值是:未使用lazy-init=true        *///        MyInitializingBeanNoLazy myInitializingBeanNoLazy = (MyInitializingBeanNoLazy) ac.getBean("myInitializingBeanSubClassUseLazy");//        System.out.println(myInitializingBeanNoLazy instanceof MyInitializingBeanNoLazy);        // 即使没用使用BeanRef,但是由于在类中引用了myInitializingBeanSubClassUseLazyByOtherRef,        // 即使myInitializingBeanSubClassUseLazyByOtherRef定义成lazy-init=true也会被加载。        BeanRef beanRef = (BeanRef) ac.getBean("beanRef");        // 在没用使用到myInitializingBeanSubClassUseLazyByOtherRef属性之前,不会加载这个属性类。        System.out.println("没有使用到BeanRef中引用的属性,属性类不会被加载");        // 使用myInitializingBeanSubClassUseLazyByOtherRef        beanRef.useProp();        /*        执行结果:        开始使用属性myInitializingBeanSubClassUseLazyByOtherRef        MyInitializingBeanNoLazy被加载了sun.misc.Launcher$AppClassLoader@2a139a55    --> 属性类被加载        ===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====        --> 属性类的初始化方法被调用        name的值是:使用了Lazy-init=true,但是被另外的类引用了。也会被加载。                    --> BeanRef本身被加载        使用属性并调用其方法完毕                                                        --> 调用BeanRef方法,触发加载         */        System.out.println("执行完毕");    }}

如果引用的类属性上也使用了@Lazy注解,那么被引用的类也是可以实现懒加载的,即使是被引用的类实现了Spring的初始化方法也可以实现。比如例子中的BeanRef这个类,在类中引用的属性上也使用了@Lazy注解,可以实现懒加载。

@Resource@Lazy  // 3.x版本的lazy注解不能使用在属性上。MyInitializingBeanNoLazy myInitializingBeanSubClassUseLazyByOtherRef;
  • 使用XML方式定义使用Spring初始化方法的类InitializingBean,并且类实现了FactoryBean接口是否可以实现懒加载??

答案是做不到,由于在afterPropertiesSet方法中调用了工厂bean的生成方法,使得当前类必须被实例化,否则无法实现工厂bean功能无法创建有工厂bean创建的对象。无论getObject方法做什么操作本类都会被实例化。即使在类上使用@Lazy注解,在getObject方法上使用@Lazy注解,在afterPropertiesSet方法上使用@Lazy注解都是无效的。因为FactoryBean是用于向容器注册bean的,它自己必须先实例化了才能执行getObject,才能向容器注册bean。简单讲,只要是实现了FactoryBean的类都无法做到懒加载。需要注意的是通过FactoryBean.getObject()方法创建的bean不会在容器启动的时候就实例化。当创建的bean用到的时候才实例化,也就是说同FactoryBean.getObject()方法创建的bean默认是懒加载的,但是一个同时实现了InitializingBean, FactoryBean这两个接口,并且在afterPropertiesSet方法里再调用了getObject()方法就可以做到在容器启动的时候就做初始化,因为实现FactoryBean接口的类会在容器启动的时候实例化,由于被实例化了所以afterPropertiesSet方法就会被容器自动调用。组合起来就实现了实时加载。

例子如下:

package com.ubuntuvim.spring.lazyloading;import org.springframework.beans.factory.FactoryBean;import org.springframework.beans.factory.InitializingBean;/** * 同时实现了Bean初始化方法和FactoryBean方法的类,是否可以做到懒加载?? * 答案是做不到,由于在afterPropertiesSet方法中调用了工厂bean的生成方法,使得当前类必须被实例化,否则无法实现工厂bean功能无法创建有工厂bean创建的对象。 * 无论getObject方法做什么操作本类都会被实例化。 * 即使在类上使用@Lazy注解,在getObject方法上使用@Lazy注解,在afterPropertiesSet方法上使用@Lazy注解都是无效的。 * 因为FactoryBean是用于向容器注册bean的,它自己必须先实例化了才能执行getObject,才能向容器注册bean。 * 简单讲,只要是实现了FactoryBean的类都无法做到懒加载。 * * 需要注意的是通过FactoryBean.getObject()方法创建的bean不会在容器启动的时候就实例化。当创建的bean用到的时候才实例化, * 也就是说同FactoryBean.getObject()方法创建的bean默认是懒加载的 * 但是一个同时实现了InitializingBean, FactoryBean这两个接口,并且在afterPropertiesSet方法里再调用了getObject()方法 * 就可以做到在容器启动的时候就做初始化,因为实现FactoryBean接口的类会在容器启动的时候实例化,由于被实例化了所以afterPropertiesSet方法就会被容器自动调用。 * 组合起来就实现了实时加载。 * * @Author: ubuntuvim * @Date: 2020/7/17 23:58 *///@Lazy  // 无效public class MyInitializingBeanNoLazyAndBeanFactoryImpl implements InitializingBean, FactoryBean {    public MyInitializingBeanNoLazyAndBeanFactoryImpl() {        System.out.println(this.getClass().getName() + "被加载了");    }    //    @Lazy  // 无效    @Override    public void afterPropertiesSet() throws Exception {        System.out.println(this.getClass().getName() + " afterPropertiesSet()方法被调用了 =====");        // 即使子类使用了@Lazy注解父类的afterPropertiesSet方法也会被调用的。        // 但是如果本类被另外一个类引用了,但是在引用的属性上也使用了@Lazy注解,那么本类可是懒加载        System.out.println("name的值是:" +  + "");        getObject();  // 即使不调用getObject方法也做不到懒加载,容器在启动的时候就会实例化当前类    }    /**     * 通过xml注入     */    String name;    public String getName() {        return name;    }    public void setName(String name) {         = name;    }    /**     * 无论这个方法做什么操作都会在容器启动的时候初始化,无法做到延迟加载     *     * @return     * @throws Exception     *///    @Lazy  // 无效    @Override    public InitFromGetObjectMethodBean getObject() throws Exception {        System.out.println(this.getClass().getName() + "的getObject方法被调用");        return new InitFromGetObjectMethodBean();    }    @Override    public Class> getObjectType() {        return InitFromGetObjectMethodBean.class;    }    @Override    public boolean isSingleton() {        return true;    }}
<?xml version="1.0" encoding="UTF-8"?>

即使没有测试类中调用这个类,容器启动时也会加载,验证结果如下:

使用属性并调用其方法完毕在未使用InitFromGetObjectMethodBean之前,这个InitFromGetObjectMethodBean类不会在容器启动的时候实例化com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazyAndBeanFactoryImpl afterPropertiesSet()方法被调用了 =====name的值是:我是一个同时实现了Bean初始化方法和FactoryBean方法的类,我即使被定义为lazy-init=true也会在启动时被实例化。   因为FactoryBean实现类必须先被实例化才能调用getObject方法向容器注册beancom.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazyAndBeanFactoryImpl的getObject方法被调用com.ubuntuvim.spring.lazyloading.InitFromGetObjectMethodBean这个bean是通过FactoryBean.getObject方法创建的com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazyAndBeanFactoryImpl的getObject方法被调用com.ubuntuvim.spring.lazyloading.InitFromGetObjectMethodBean这个bean是通过FactoryBean.getObject方法创建的com.ubuntuvim.spring.lazyloading.InitFromGetObjectMethodBean@64cd705f执行完毕BUILD SUCCESSFUL in 20s80 actionable tasks: 1 executed, 79 up-to-date02:53:15: Task execution finished 'LazyLoadingXmlContextTest.main()'.

按照XML的配置顺序,这个类在最后面被加载了。

更深入的原因我们从Spring源码层级分析。按照如下流程找到方法,




springboot Autowired 延迟加载_加载_02

调用流程



DefaultListableBeanFactory.preInstantiateSingletons方法是最后的核心方法。这个方法内部实例化了实现FactoryBean的类并调用getObject方法向容器注册bean,并且这个方法只有非懒加载的bean能进入到这里。。

// 实例化所有除了Spring内部的单例(懒加载的除外)@Overridepublic void preInstantiateSingletons() throws BeansException {    if (logger.isTraceEnabled()) {        logger.trace("Pre-instantiating singletons in " + this);    }    // Iterate over a copy to allow for init methods which in turn register new bean definitions.    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.    // 创建BeanDefinitionName副本,用于后续遍历,以允许init等方法注册新的bean定义    List beanNames = new ArrayList<>(this.beanDefinitionNames);    // Trigger initialization of all non-lazy singleton beans...    // 遍历所有的beanName,通过beanName获取到对应的bean实例    for (String beanName : beanNames) {        // 根据bean名称拿到bean定义        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);        // bean定义非抽象类,是单例,非懒加载        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {            // 判断是否是FactoryBean            // Spring有两种类型的Bean,一种普通Bean,一种是工厂Bean即FactoryBean,FactoryBean和普通Bean不同,            // 它返回的对象不是一个指定类型的对象,而是根据FactoryBean.geteObject()返回对象,返回的对象就是T类型的,            // 创建出来的对象是否为单例是根据Bean定义中的isSingleton属性决定的(默认是单例)            if (isFactoryBean(beanName)) {                // 通过getBean(&beanName)拿到是FactoryBean本身,FACTORY_BEAN_PREFIX=&                // 通过getBean(beanName)拿到的是FactoryBean创建的bean实例                // 比如  MyInitializingBeanNoLazyAndBeanFactoryImpl implements FactoryBean                // 拿到的就是myInitializingBeanNoLazyAndBeanFactoryImpl这个bean实例。                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);                if (bean instanceof FactoryBean) {                    final FactoryBean> factory = (FactoryBean>) bean;                    // 判断bean是否需要急切初始化,实现了FactoryBean的类isEagerInit都是false,                    // 只有SmartFactoryBean的实现类可以控制这个属性值,但是这个接口是提供给Spring框架本身内部使用的不建议开发者使用                    boolean isEagerInit;                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {                        isEagerInit = AccessController.doPrivileged((PrivilegedAction)                                        ((SmartFactoryBean>) factory)::isEagerInit,                                getAccessControlContext());                    }                    else {                        isEagerInit = (factory instanceof SmartFactoryBean &&                                ((SmartFactoryBean>) factory).isEagerInit());                    }                    if (isEagerInit) {                        // 通过beanName获取bean实例                        getBean(beanName);                    }                }            }            else {                // 普通的bean直接通过beanName获取bean实例                getBean(beanName);  // 转到AbstractBeanFactory            }        }    }    //  方面后面的代码省略。。。。

经过上述代码之后, MyInitializingBeanNoLazyAndBeanFactoryImpl就被实例化好了。但是也只是FactoryBean实现类本身被实例化了,还没真正调用getObject方法注册bean,只有在使用到被注册的bean的时候才会执行getObject方法,也就是说通过FactoryBean.getObject()方法创建的bean默认是懒加载的。