@Lazy原理与实战

文章目录


前言

  @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 自动扫描组件 ClassPathBeanDefinitionScannerprocessCommonDefinitionAnnotations() 中设置 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的回调,实际执行的也是DefaultListableBeanFactorydoResolveDependency方法。

三、解决循环依赖的问题

  使用@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,走DefaultListableBeanFactorydoResolveDependency方法,从Spring的容器中获取bean,获取不到就去创建,走doCreateBean的流程。(注意,同样无法解决两者都是构造方法的循环依赖问题,但是可以解决原型bean的循环依赖)

相关推荐
callJJ1 分钟前
Java 线程池使用指南:基于 Spring Boot 3.x + JDK 17 的入门与实践
java·开发语言·spring boot·线程池·多线程编程
Elias不吃糖4 分钟前
RabbitMQ vs Kafka 简单总结
java·分布式·kafka·rabbitmq
ch.ju4 分钟前
Java Programming Chapter 4——Error in compilation: it cannot be overwritten.
java·开发语言
m0_7373025810 分钟前
OpenClaw:不止于对话,能真正动手的开源 AI 智能体
服务器
nice_lcj52011 分钟前
排序(4)-归并排序专题——归并排序的分治美学
java·数据结构·算法·排序算法
我登哥MVP14 分钟前
SpringCloud 核心组件解析:服务调用和负载均衡
java·spring boot·后端·spring·spring cloud·java-ee·负载均衡
插件开发14 分钟前
英伟达cuda程序通用性关键 geforce 20xx代到最新版 在20xx上编译的c++程序可以通用吗?
java·c++·人工智能
JackSparrow41415 分钟前
彻底理解Java NIO(三)Java实现 I/O多路复用+Reactor模式及开源框架代码解读
java·c语言·开发语言·后端·nio·reactor模式
程序员黑豆16 分钟前
AI全栈开发 - Java:数据类型
java·前端
云飞云共享云桌面16 分钟前
东莞制造业研发降本方案:1 台云主机承载 10 人 SolidWorks,钣金操作秒响应
linux·运维·服务器·安全·电脑