文章目录
- 前言
- 一、@Lazy的解析
- [二、@Lazy Bean的处理](#二、@Lazy Bean的处理)
- 三、解决循环依赖的问题
前言
@Lazy
是Spring提供的注解,用于实现懒加载
功能。该注解既可以修饰方法,也可以修饰类,字段等:
- 被
@Lazy
修饰的类,不会在Spring容器的refresh方法中进行实例化,而是只有在第一次注入或使用该 Bean 时才会实例化。
java
@Lazy
@Component
public class MyService {
// ...
}
- 被
@Lazy
修饰的方法,该方法创建的 Bean 是懒加载的,只有第一次被注入或调用时才会初始化。
java
@Configuration
public class AppConfig {
@Bean
@Lazy
public ExpensiveBean expensiveBean() {
return new ExpensiveBean();
}
}
- 被
@Lazy
修饰的字段,会延迟注入。
java
@Component
public class SomeComponent {
@Autowired
@Lazy
private AnotherService anotherService;
}
- 被
@Lazy
修饰的构造函数参数或方法参数,参数所代表的 Bean 会被延迟注入。
java
@Component
public class MyComponent {
private final HeavyService heavyService;
public MyComponent(@Lazy HeavyService heavyService) {
this.heavyService = heavyService;
}
}
一、@Lazy的解析
@Lazy
的解析时机,有两个场景:
场景 | 处理逻辑 |
---|---|
@ComponentScan 自动扫描组件 |
ClassPathBeanDefinitionScanner → processCommonDefinitionAnnotations() 中设置 lazyInit |
手动注册配置类(如 registerBean(SomeClass.class) ) |
调用 processCommonDefinitionAnnotations() 设置 lazyInit |
1.1、场景一
对应的案例工程:
java
public class Demo1 {
public static void main(String[] args) {
// 初始化一个空容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 手动注册类 A,而不是使用 @ComponentScan
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(context);
reader.register(A.class);
// 刷新容器,触发 Bean 生命周期
context.refresh();
System.out.println("IOC 容器已启动");
// 懒加载:此时 A 还未被创建
System.out.println("开始获取 A");
A a = context.getBean(A.class);
System.out.println("A 获取完成: " + a);
}
}
@Lazy
@Component
class A {
public A() {
System.out.println("A 被创建");
}
}
在AnnotationConfigUtils#processCommonDefinitionAnnotations
时调用。
1.2、场景二
对应的案例工程:
java
public class Demo2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
System.out.println("IOC容器已启动");
// 不会实例化 A,只有调用 getBean 时才会触发创建
A a = context.getBean(A.class);
System.out.println("B 已获取: " + a);
}
}
@Configuration
@ComponentScan
class Config {
}
@Lazy
@Component
class B {
public B() {
System.out.println("B 初始化");
}
}
对应的解析时机是,refresh方法中,调用所有bean工厂的后置处理器,ConfigurationClassPostProcessor
扫描并解析@ComponentScan
注解的逻辑中:
parse最终会进入doScan方法:
同样是在processCommonDefinitionAnnotations
中设置bean定义的lazy属性:
无论上述哪一个场景,最终的目的都是为了将bean定义的lazyInit属性进行设置,用于后续的判断。
二、@Lazy Bean的处理
对于标注了@Lazy的Bean的处理,主要体现在两个地方:
- 初始化所有非懒加载的单例bean
- bean生命周期的属性注入
在初始化所有非懒加载的单例bean时,如果发现某个bean定义的lazyInit属性为true,就不会对该bean进行实例化。
而在bean生命周期的依赖注入阶段,当发现了某个注入点的bean定义的lazyInit属性为true:
则会在buildLazyResolutionProxy
内部直接生成一个代理对象并注入给对应的字段:
生成代理对象。
在实际调用时,执行TargetSource
的回调,实际执行的也是DefaultListableBeanFactory
的doResolveDependency
方法。
三、解决循环依赖的问题
使用@Lazy注解,也可以解决循环依赖的问题:
java
public class Demo3 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config1.class);
}
}
@Configuration
@ComponentScan
class Config1{
}
@Component
class C{
@Lazy
@Autowired
private D d;
}
@Component
class D{
@Lazy
@Autowired
private C c;
}
在上面的场景中,当C实例化完成,依赖注入D时,发现D是加上了@Lazy
注解,就不会去Spring容器中去找D,走找不到创建的流程,而是直接生成一个D的代理对象,注入给C的D属性,然后C执行初始化操作,也就打破了循环依赖。
当C中的D属性被调用时,才会回调切面的TargetSource
,走DefaultListableBeanFactory
的doResolveDependency
方法,从Spring的容器中获取bean,获取不到就去创建,走doCreateBean
的流程。(注意,同样无法解决两者都是构造方法的循环依赖问题,但是可以解决原型bean的循环依赖)