懒加载的几个疑问?? 说明: 本篇是基于Spring5.x版本做的演示。不同的版本可能效果不一样,比如@Lazy注解在3.1版本是不支持在属性、构造方法、参数上使用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Component @Lazy public class BeanOne { public BeanOne () { System.out.println("\n\nbeanFactory开始加载BeanOne" ); } public void sayHelloBeanOne () { System.out.println("调用BeanOne的方法" ); } } @Component @Lazy public class BeanTwo { public BeanTwo () { System.out.printf("开始加载BeanTwo" ); } @Resource BeanOne beanOne; public void invokeBeanOneMethod () { System.out.println("在BeanTwo里面调用BeanOne的方法" ); beanOne.sayHelloBeanOne(); } } @Configuration @ComponentScan("com.ubuntuvim.spring.lazyloading") public class BeanLoadingConfig {}
验证如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.ubuntuvim.spring.lazyloading;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class LazyLoadingTest { public static void main (String[] args) { ApplicationContext ac = new AnnotationConfigApplicationContext(BeanLoadingConfig.class); BeanTwo beanTwo = (BeanTwo) ac.getBean("beanTwo" ); System.out.println("未调beanTwo 的方法之后,不应该加载BeanOne(但事与愿违)" ); beanTwo.invokeBeanOneMethod(); BeanOne beanOne = (BeanOne) ac.getBean("beanOne" ); } }
对于实现了Spring初始化方法的bean,设置lazy-init
属性为true
是否能起到延迟加载的效果??
实现了InitializingBean
的bean本身使用了@Lazy
注解是可以延迟加载的。但是如果是父类实现了InitializingBean
的方法,但是没有使用@Lazy
注解,而是在子类中使用了@Lazy
注解,那么父类的spring初始化方法是会被执行的,@Lazy
注解只是对当前类起效果,并不能控制父类的懒加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Component @Lazy public class MyInitializingBean implements InitializingBean { @Override public void afterPropertiesSet () throws Exception { System.out.println("实现了InitializingBean的afterPropertiesSet()方法被调用了" ); } } @Component public class MyInitializingBeanNoLazy implements InitializingBean { public MyInitializingBeanNoLazy () { System.out.println("MyInitializingBeanNoLazy被加载了" ); } @Override public void afterPropertiesSet () throws Exception { System.out.println("===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====" ); } } @Component @Lazy public class MyInitializingBeanSubClass extends MyInitializingBeanNoLazy { public MyInitializingBeanSubClass () { System.out.println("MyInitializingBeanNoLazy的子类使用@Lazy注解,子类被加载了" ); } }
执行效果,MyInitializingBeanNoLazy一开始就被加载了。
1 2 3 4 5 6 7 8 9 10 11 12 13 MyInitializingBeanNoLazy被加载了 ===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 ===== 开始加载BeanTwo未调beanTwo 的方法之后,不应该加载BeanOne(但事与愿违) 在BeanTwo里面调用BeanOne的方法 beanFactory开始加载BeanOne 调用BeanOne的方法 在未使用MyInitializingBean之前,如果bean使用了@Lazy注解,即使这个bean实现了InitializingBean的接口也不会被加载 实现了InitializingBean的afterPropertiesSet()方法被调用了 BUILD SUCCESSFUL in 9s 80 actionable tasks: 2 executed, 78 up-to-date 21:42:01: Task execution finished 'LazyLoadingTest.main()'.
使用XML方式定义使用Spring初始化方法的类是否可以实现懒加载??
定义一个实现了Spring初始化方法的Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Component public class MyInitializingBeanNoLazy implements InitializingBean { public MyInitializingBeanNoLazy () { System.out.println(this .getClass().getName() + "被加载了" ); } @Override public void afterPropertiesSet () throws Exception { System.out.println("===== MyInitializingBeanNoLazy的afterPropertiesSet()方法被调用了 =====" ); System.out.println("name的值是:" + this .name + "\n" ); } String name; public String getName () { return name; } public void setName (String name) { this .name = name; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd " > <context:component-scan base-package ="com.ubuntuvim.spring.lazyloading" /> <bean name ="myInitializingBeanSubClassUseLazy" class ="com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazy" lazy-init ="true" > <property name ="name" value ="使用了Lazy-init=true" /> </bean > <bean name ="myInitializingBeanSubClassUseLazyByOtherRef" class ="com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazy" lazy-init ="true" > <property name ="name" value ="使用了Lazy-init=true,但是被另外的类引用了。也会被加载。如果在引用的属性上使用了@Lazy注解,那么也可以实现懒加载!" /> </bean > <bean name ="myInitializingBeanSubClassNotUseLazy" class ="com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazy" > <property name ="name" value ="未使用lazy-init=false" /> </bean > </beans >
验证结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.ubuntuvim.spring.lazyloading;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class LazyLoadingXmlContextTest { public static void main (String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("classpath*:lazy-loading-test.xml" ); BeanRef beanRef = (BeanRef) ac.getBean("beanRef" ); System.out.println("\n\n没有使用到BeanRef中引用的属性,属性类不会被加载" ); beanRef.useProp(); System.out.println("执行完毕" ); } }
如果引用的类属性上也使用了@Lazy
注解,那么被引用的类也是可以实现懒加载的,即使是被引用的类实现了Spring的初始化方法也可以实现。比如例子中的BeanRef
这个类,在类中引用的属性上也使用了@Lazy
注解,可以实现懒加载。
1 2 3 @Resource @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方法就会被容器自动调用。 组合起来就实现了实时加载。
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.ubuntuvim.spring.lazyloading;import org.springframework.beans.factory.FactoryBean;import org.springframework.beans.factory.InitializingBean;public class MyInitializingBeanNoLazyAndBeanFactoryImpl implements InitializingBean , FactoryBean <InitFromGetObjectMethodBean > { public MyInitializingBeanNoLazyAndBeanFactoryImpl () { System.out.println(this .getClass().getName() + "被加载了" ); } @Override public void afterPropertiesSet () throws Exception { System.out.println(this .getClass().getName() + " afterPropertiesSet()方法被调用了 =====" ); System.out.println("name的值是:" + this .name + "\n" ); getObject(); } String name; public String getName () { return name; } public void setName (String name) { this .name = name; } @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 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd " > <context:component-scan base-package ="com.ubuntuvim.spring.lazyloading" /> <bean name ="myInitializingBeanNoLazyAndBeanFactoryImplByOtherRef" class ="com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazyAndBeanFactoryImpl" lazy-init ="true" > <property name ="name" value ="我是一个同时实现了Bean初始化方法和FactoryBean方法的类,我即使被定义为lazy-init=true也会在启动时被实例化。 因为FactoryBean实现类必须先被实例化才能调用getObject方法向容器注册bean" /> </bean > </beans >
即使没有测试类中调用这个类,容器启动时也会加载,验证结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 使用属性并调用其方法完毕 在未使用InitFromGetObjectMethodBean之前,这个InitFromGetObjectMethodBean类不会在容器启动的时候实例化 com.ubuntuvim.spring.lazyloading.MyInitializingBeanNoLazyAndBeanFactoryImpl afterPropertiesSet()方法被调用了 ===== name的值是:我是一个同时实现了Bean初始化方法和FactoryBean方法的类,我即使被定义为lazy-init=true也会在启动时被实例化。 因为FactoryBean实现类必须先被实例化才能调用getObject方法向容器注册bean com.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 20s 80 actionable tasks: 1 executed, 79 up-to-date 02:53:15: Task execution finished 'LazyLoadingXmlContextTest.main()'.
按照XML的配置顺序,这个类在最后面被加载了。
更深入的原因我们从Spring源码层级分析。按照如下流程找到方法,
graph TD;
ClassPathXmlApplicationContext --> AbstractApplicationContext.refresh;
AbstractApplicationContext.refresh --> finishBeanFactoryInitialization方法;
finishBeanFactoryInitialization方法 --> beanFactory.preInstantiateSingletons方法;
beanFactory.preInstantiateSingletons方法 --> DefaultListableBeanFactory.preInstantiateSingletons方法;
DefaultListableBeanFactory.preInstantiateSingletons方法是最后的核心方法。这个方法内部实例化了实现FactoryBean的类并调用getObject方法向容器注册bean,并且这个方法只有非懒加载的bean能进入到这里。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 @Override public void preInstantiateSingletons () throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this ); } List<String> beanNames = new ArrayList<>(this .beanDefinitionNames); for (String beanName : beanNames) { RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { if (isFactoryBean(beanName)) { Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { final FactoryBean<?> factory = (FactoryBean<?>) bean; boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } } else { getBean(beanName); } } }
经过上述代码之后, MyInitializingBeanNoLazyAndBeanFactoryImpl就被实例化好了。但是也只是FactoryBean实现类本身被实例化了,还没真正调用getObject方法注册bean,只有在使用到被注册的bean的时候才会执行getObject方法,也就是说通过FactoryBean.getObject()方法创建的bean默认是懒加载的。