SpringBoot:使用 @Lazy 注解懒加载

本文探讨了懒加载在SpringBoot中的实现及其对应用程序性能的影响。通过使用@Lazy注解,可以延迟对象实例化,直到首次使用时才加载到IOC容器中,从而减少启动时间。但需要注意的是,延迟加载可能会导致运行时错误,而非启动阶段,因此在设计时需谨慎考虑。

为什么需要懒加载?

我们知道,在 SpringBoot 应用程序启动的时候,会实例化一些对象加入到 IOC 容器里边,这个过程是非常耗时的,那我们想要减少这个耗时的过程就需要 @Lazy 注解

对象加入容器的时机

如下代码

package com.startdusk.forgot.service;

import org.springframework.stereotype.Component;

@Service
public class LazyService {
    public LazyService() {
        System.out.println("LazyService add to ioc container");
    }

    public void print() {
        System.out.println("lazy");
    }
}

由于我们在 LazyService 中打上了 @Service 注解,那么当程序启动的时候,LazyService 就会被立即实例化,我们在 LazyService 写了个无参的构造函数来测试,启动程序,会打印出

那么说明,在打上了 @Service 注解后,在程序启动的时候就立即实例化对象加入到容器

使用 @Lazy 延迟加入容器

那我们来改写下代码,延迟加入容器:

package com.startdusk.forgot.service;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
@Lazy
public class LazyService {
    public LazyService() {
        System.out.println("LazyService add to ioc container");
    }

    public void print() {
        System.out.println("lazy");
    }
}

再来运行一下程序:

看到运行的结果,并没有打印 “LazyService add to ioc container” 也就没有实例化 LazyService ,说明 @Lazy 注解起作用了,我们延迟了对象加入容器的时机

一个奇怪的现象

那么在实际情况中,我们会在 controller 中调用这个 service,即在 controller 中注入这个对象:

package com.startdusk.forgot.api.v1;

import com.startdusk.forgot.service.LazyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/v1/lazy")
public class LazyController {

    @Autowired
    private LazyService lazyService;

    @GetMapping("/test")
    public String test() {
        lazyService.print();
        return "Hello, world";
    }
}

保持 LazyService 中的 @Lazy,我们运行程序

发现 LazyService 居然被实例化了,难道打上 @Lazy 注解的类被注入之后 @Lazy 就不起作用了吗?我们来看下这个 LazyController 的代码,这里其实我们也把 LazyController 加入了容器(即打上了 @RestController 注解),在 SpringBoot 中,加入 IOC 容器就必须实例化对象,而实例化对象就要求,这个对象的里面的成员变量,方法都要被初始化,就包括 lazyService 这个要被注入的变量,即使 LazyService 打上了 @Lazy 注解。

解决这个问题,我们也需要在 LazyController 打上 @Lazy 注解,才能延迟加入 IOC 容器

package com.startdusk.forgot.api.v1;

import com.startdusk.forgot.service.LazyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/v1/lazy")
@Lazy
public class LazyController {

    @Autowired
    private LazyService lazyService;

    @GetMapping("/test")
    public String test() {
        lazyService.print();
        return "Hello, world";
    }
}

@Lazy 会延迟到什么时候

会延迟到对象被调用的时候。如,运行程序,访问 localhost:8080/v1/lazy/test 就是访问 test 这个方法,那么就会调用 lazyService.print() 就会打印出:

我们可以看到,当被注入的对象被调用的时候,才会把对象加入 IOC 容器,然后注入对象

使用 @Lazy 的缺点

我们来调整下代码:
LazyService 注释掉 @Service 和 @Lazy

package com.startdusk.forgot.service;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

// @Service
// @Lazy
public class LazyService {
    public LazyService() {
        System.out.println("LazyService add to ioc container");
    }

    public void print() {
        System.out.println("lazy");
    }
}

那么运行代码,再来访问下 localhost:8080/v1/lazy/test
,那么,程序会给我们抛一个错误:

Error creating bean with name 'lazyController': Unsatisfied dependency expressed through field 'lazyService';......

就是找不到这个这个注入的对象。那么这个报错是在程序运行中报错,而不是程序启动的时候就报错了。那么我们在 LazyController 中去掉 @Lazy

package com.startdusk.forgot.api.v1;

import com.startdusk.forgot.service.LazyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/v1/lazy")
public class LazyController {

    @Autowired
    private LazyService lazyService;

    @GetMapping("/test")
    public String test() {
        lazyService.print();
        return "Hello, world";
    }
}

启动程序,那么就会在程序启动的时候直接报错,不让你运行

Field lazyService in com.startdusk.forgot.api.v1.LazyController required a bean of type 'com.startdusk.forgot.service.LazyService' that could not be found.

### @Lazy注解的作用与使用场景 #### 1. @Lazy注解的核心作用 在Spring框架中,@Lazy注解的主要作用是延迟Bean的初始化。默认情况下,Spring容器会在启动时立即实例化所有的单例(Singleton)Bean[^3]。然而,在某些场景下,这种行为可能会导致不必要的资源消耗或延长应用的启动时间。通过使用@Lazy注解,可以将Bean的初始化推迟到第一次实际使用时才进行。 例如,当一个Bean依赖于其他复杂的初始化逻辑或外部服务时,延迟加载可以避免在应用启动阶段执行这些操作,从而优化启动性能。 #### 2. 使用方法 @Lazy注解可以应用于类、方法或字段上,具体用法如下: ##### (1)应用于类 当@Lazy注解标记在一个类上时,表示该类的所有Bean定义都会被延迟加载。 ```java @Component @Lazy public class MyLazyComponent { // Bean 的初始化会被延迟 } ``` ##### (2)应用于方法 当@Lazy注解标记在一个@Bean方法上时,表示该方法定义的Bean会被延迟加载。 ```java @Configuration public class AppConfig { @Bean @Lazy public MyService myService() { return new MyService(); } } ``` ##### (3)应用于字段 当@Lazy注解标记在一个@Autowired字段上时,表示该字段注入的Bean会被延迟加载。 ```java @Component public class MyComponent { @Autowired @Lazy private MyService myService; } ``` #### 3. 延迟加载的原理 当使用@Lazy注解时,Spring会创建一个代理对象来代替实际的Bean实例。只有当程序首次访问该Bean时,Spring才会触发实际的初始化逻辑[^1]。这种方式通过动态代理机制实现了延迟加载的效果。 #### 4. 使用场景 @Lazy注解适用于以下场景: - **优化启动性能**:对于一些不常使用的Bean,可以通过延迟加载减少应用启动时的资源消耗。 - **解决循环依赖问题**:在某些情况下,循环依赖会导致Bean无法正常初始化。通过延迟加载,可以缓解此类问题。 - **按需加载**:在微服务架构中,某些模块可能仅在特定条件下才需要加载。通过延迟加载,可以实现更灵活的模块化设计。 #### 示例代码 以下是一个完整的示例,展示如何使用@Lazy注解实现延迟加载: ```java @SpringBootApplication public class LazyApp { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(LazyApp.class, args); System.out.println("Application started."); // 手动触发Bean的初始化 context.getBean(MyLazyComponent.class); } } @Component @Lazy class MyLazyComponent { public MyLazyComponent() { System.out.println("MyLazyComponent initialized."); } } ``` 运行结果表明,`MyLazyComponent`的初始化发生在手动调用`context.getBean`之后,而非应用启动时。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值