Spring的懒加载和延迟注入是什么?两者有何区别?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
回答话术
用过 Spring 的人可能都知道,我们可以在类、配置类的工厂方法或者属性上添加 @Lazy
注解,从而实现懒加载:
@Lazy // 懒加载
@Component
public class Example {
@Lazy // 延迟注入
@Autowired
private Foo foo;
}
实际上,在上述代码中,生效的机制有所不同:
- 当加在类或工厂方法上:表示 Bean 的懒加载,即 Spring 容器启动时将不会主动提前初始化 Bean,直到有依赖该 Bean 的其他 Bean 被加载,或者主动从容器获取 Bean 时才加载。
- 加在需要依赖注入的属性上:表示依赖的延迟注入,即注入的 Bean 实际上是一个延迟注入代理对象,等真正调用它的方法时才会从容器获取 Bean。
简而言之,懒加载可以避免 Spring 容器启动时“主动”触发 Bean 的加载,而延迟注入可以避免 Bean 因为被其他加载的 Bean 依赖而“被动”触发加载,那么当两者同时配置时,才能实现真正的懒加载。
顺带一提,我们通常会说加
@Lazy
注解可以不依赖三级缓存解决循环依赖问题,实际上严格点说,只有延迟注入可以完美做到这个效果,单纯把 Bean 声明为懒加载实际上是不行的。
问题详解
1. 懒加载
当我们在类或工厂方法上加 @Lazy
注解时,实际上等同于在 BeanDefinition
中设置 setLazyInit(true)
,按照 Spring 的说法,这些 Bean 将不会在 Spring 工厂启动时急切的初始化。
这里“急切的初始化”实际上是指,在 Spring 容器执行 AbstractApplicationContext.refresh
方法启动的过程中,会在 finishBeanFactoryInitialization
这一步调用内部的 BeanFactory
的 preInstantiateSingletons
直接将所有的单例 Bean 创建出来。
参见 DefaultListableBeanFactory
的 preInstantiateSingletons
方法:
@Override
public void preInstantiateSingletons() throws BeansException {
// ... ...
// 触发所有非懒加载的单例 Bean 的初始化
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 执行初始化操作
}
}
// ... ...
}
不过,虽然懒加载的 Bean 不会在这里主动的初始化,但是如果这个 Bean 被其他的 Bean 所依赖,并且那个 Bean 没有指定懒加载,那么此时还是会被动的触发加载,比如:
@Lazy
@Component
public class LazyInitBena { }
@Component
public class Foo {
@Autowired
private LazyInitBean lazyInitBean;
}
在上述示例中,LazyInitBean
的懒加载实际上是无效的,因为依赖它的 Foo
并没有指定懒加载,那么 Foo
被主动触发初始化后,为了完成依赖注入,依然会触发 LazyInitBean
的初始化。
关于 Spring 容器的启动,请参见:✅ Spring 容器的启动过程?
2. 延迟注入
当我们在属性上添加 @Lazy
注解时,Spring 会为依赖注入的 Bean 生成一个代理类,此时注入的 Bean 实际上是通过代理生成的“假对象”,等到真正要调用它的方法的时候,才会在代理对象里面从 Spring 容器获取真正的 Bean。
2.1. 延迟注入代理
一切的秘密都在 DefaultListableBeanFactory
的 resolveDependency
这个方法中:
@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
// 依赖是 Optional 类型
if (javaUtilOptionalClass == descriptor.getDependencyType()) {
return new OptionalDependencyFactory().createOptionalDependency(descriptor, requestingBeanName);
}
// 依赖是 ObjectFactory 或 ObjectProvider 类型
else if (ObjectFactory.class == descriptor.getDependencyType() ||
ObjectProvider.class == descriptor.getDependencyType()) {
return new DependencyObjectProvider(descriptor, requestingBeanName);
}
// 依赖是 javax.inject.Provider 类型
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName);
}
else {
// 依赖是普通类型,但是需要懒加载
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
if (result == null) {
// 对象不需要懒加载
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;
}
}
我们重点关注 getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary
,从字面上不难理解,实际上这里会尝试获取获取一个懒加载代理,而最终拿到的懒加载代理则如下:
// ContextAnnotationAutowireCandidateResolver.buildLazyResolutionProxy
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final String beanName) {
Assert.state(this.getBeanFactory() instanceof DefaultListableBeanFactory, "BeanFactory needs to be a DefaultListableBeanFactory");
final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)this.getBeanFactory();
// 注意,这里为代理声明了一个对象源
TargetSource ts = new TargetSource() {
public Class<?> getTargetClass() {
return descriptor.getDependencyType();
}
public boolean isStatic() {
return false;
}
public Object getTarget() {
// 当需要使用对象时,才从 BeanFactory 中获取对象
Object target = beanFactory.doResolveDependency(descriptor, beanName, (Set)null, (TypeConverter)null);
if (target == null) {
Class<?> type = this.getTargetClass();
if (Map.class == type) {
return Collections.EMPTY_MAP;
} else if (List.class == type) {
return Collections.EMPTY_LIST;
} else if (Set.class != type && Collection.class != type) {
throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), "Optional dependency not present for lazy injection point");
} else {
return Collections.EMPTY_SET;
}
} else {
return target;
}
}
public void releaseTarget(Object target) {
}
};
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
Class<?> dependencyType = descriptor.getDependencyType();
if (dependencyType.isInterface()) {
pf.addInterface(dependencyType);
}
return pf.getProxy(beanFactory.getBeanClassLoader());
}
在这段代码里面,Spring 懒加载代理类声明了一个 TargetSource
,当代理对象通过 TargetSource
获取实际的对象时,将会通过 BeanFactory 获取真正的 Bean。
际上,单纯的延迟注入也不能真正的做到“懒加载”,因为虽然由于注入延迟导致 Bean 可以避免被动加载,但是如果 Bean 没有声明“懒加载”,那依然会在 Spring 容器启动时被“急切的初始化”。
2.2. 容器提供者
另外,通过代码我们也不难看出,实际上除了通过延迟注入代理实现延迟注入外,我们也可以通过将属性的类型修改为 ObjectFactory
、ObjectProvider
或者 Provider
实现延迟注入:
public class Example {
@Autowired
private ObjectFactory<Foo> fooObjectFactory;
@Autowired
private ObjectProvider<Foo> fooObjectProvider;
@Autowired
private Provider<Foo> fooProvider;
}
我们点进它们各自的实现,都会发现注入的对象最终都会调用 BeanFactory.getBean
创建并获取 Bean。