前言
大家好,我是java小面,今天我们继续前面Spring文章比较核心的Bean内容的探讨,这次来探讨的是关于延迟初始化Bean是否会影响到依赖注入的问题,依赖注入一直以来都是Spring面试中的核心,很多面试官都很喜欢围绕着依赖注入和依赖查找去考察面试人对Spring的理解深度以使用情况。
Bean延迟初始化(Lazy Initialization)
它的使用很简单,可以通过xml来配置和Java 注解@Lazy来为Bean的初始化进行配置。
那么问题来了,当某个Bean被定义为延迟初始化,那么当我们依赖注入拿到时,延迟和非延迟对象之间存在着什么差异呢?
案例分析
代码语言:javascript复制/**
* @author Java面试教程
* @date 2022-12-18 21:14
*/
@Configuration
public class Demo {
public static void main(String[] args)
{
//创建BeanFactory容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//注册当前类,主要目的是获取@Bean
applicationContext.register(Demo.class);
//启动应用上下文
applicationContext.refresh();
System.out.println("Spring应用上下文已启动...");
//依赖查找
UserFactory bean = applicationContext.getBean(UserFactory.class);
//关闭应用上下文
applicationContext.close();
}
@Bean(initMethod = "initUserFactory")
@Lazy
public UserFactory createUserFactory(){
System.out.println("初始化了");
return new DefaultUserFactory();
}
}
我们在createUserFactory这个方法上打上@Lazy注解,来标记它是一个延迟初始化。
代码语言:javascript复制@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* Whether lazy initialization should occur.
*/
boolean value() default true;
}
我们看一下关于这个@Lazy注解,我们可以看到这么一个关键字,default true ,当value=true的时候默认值是延迟加载的意思。
代码语言:javascript复制boolean value() default true;
学到这里,小面的脑子里就又冒出了一个疑问了,Java注解是一个静态的东西,一旦标记上了,除非重新编译打包,不然没办法修改才对,与其要我标上@Lazy特地打个false,那还不如一开始不打上这个注解呢?可能有什么其他意义吧?小面还没学到那个地步,真的是小小的脑袋,大大的疑问,如果有懂这方面的大佬,不妨在群里告诉一下撒。
略过这个疑问,我们来运行一下这个Main方法看看两者的区别
这是使用了@Lazy(value = true)的时候
代码语言:javascript复制Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
Spring应用上下文已启动...
初始化了
org.example.factory.DefaultUserFactory@2e222612
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'
这是使用了@Lazy(value = false)的时候
代码语言:javascript复制Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
初始化了
Spring应用上下文已启动...
org.example.factory.DefaultUserFactory@77eca502
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'
这是我使用了@Lazy(value = true) 且把依赖查找注释掉的时候
代码语言:javascript复制// UserFactory bean = applicationContext.getBean(UserFactory.class);
// System.out.println(bean);
我们看看它打印了什么
代码语言:javascript复制Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
Spring应用上下文已启动...
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'
这样子,两者的使用区别是不是就很明显了,说明使用了@Lazy的时候,代表我这个Bean是按需初始化,我需要使用它的时候才会去初始化它。
这就是它们两者的主要区别。
继续学习
我们来看看它在实现上是不是有什么不一样?我们来看一下refresh方法
代码语言:javascript复制//启动应用上下文
applicationContext.refresh();
它有这么核心的一段代码
代码语言:javascript复制// 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);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
其中我们看 finishBeanFactoryInitialization(beanFactory) 按照方法的名称的意思就叫做完成BeanFactory的初始化
我们看看这句注解 ”Instantiate all remaining (non-lazy-init) singletons“ 它的意思大概是,它会去初始化所有非延迟初始化的单体类或者Bean。
我们看一下这个方法的实现
代码语言:javascript复制protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// 如果没有bean后处理器,则注册一个默认的嵌入式值解析器(例如PropertyPlaceholderConfigurer bean)注册任何之前:
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有剩余的(非lazy-init)单例
beanFactory.preInstantiateSingletons();
}
圈重点看这一句,它的意思是去初始化剩余的东西,
代码语言:javascript复制// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有剩余的(非lazy-init)单例
beanFactory.preInstantiateSingletons();
是否意味着在应用文上下启动的时候,有这么一个前置动作,执行了什么把需要初始化的Bean分了类,然后导致标识为正常初始化,非lazy-init的类或对象被定义成了剩余的单例。
总结
通过源码的深入,我们其实可以看出,延迟加载和非延迟加载在定义的时候,Bean注册的时候是没有区别的,在依赖查找和依赖注入的时候就明显不同了,非延迟是在上下文启动之前就初始化Bean了,而延迟是在Bean初始化之后按需加载。