logo头像

From zero to HERO

合理使用延迟加载优化 Spring Boot

本文于 1327 天之前发表,文中内容可能已经过时。

1. 前言

随着我们项目的不断迭代 Bean 的数量会大大增加,如果都在启动时进行初始化会非常耗时。Spring Boot 允许延迟初始化应用程序, 也就是根据需要初始化 Spring Bean,而不是在 Spring Boot 启动时创建所有的 Bean。这样的就可以减少应用程序启动花费的时间。延迟初始化通常又被称为“懒加载”。

2. 延迟初始化

Spring Boot 中的延迟初始化可分为全局延迟初始化局部初始化

注意:以下特性在 Spring Boot 2.2.x 中存在

2.1 全局初始化

全局初始化我们可以通过编程的方式来实现,需要我们来改变 Spring Boot Main方法的写法。

通常我们的 Main 方法是这样的,注意这里还没声明全局懒加载

/**
 * @author felord.cn
 * @since 2020/3/31 22:53
 */
@SpringBootApplication
public class DemoSpringbootApplication {
    @Lazy
    public static void main(String[] args) {
        SpringApplication.run(DemoSpringbootApplication.class,args);
    }
}

全局懒加载写法一:

/**
 * @author felord.cn
 * @since 2020/3/31 22:53
 */
@SpringBootApplication
public class DemoSpringbootApplication {
    @Lazy
    public static void main(String[] args) {
        SpringApplication sa = new SpringApplication(DemoSpringbootApplication.class);

        sa.setLazyInitialization(true);
        sa.run(args);
    }
}

全局懒加载写法二:

/**
 * @author felord.cn
 * @since 2020/3/31 22:53
 */
@SpringBootApplication
public class DemoSpringbootApplication {
    @Lazy
    public static void main(String[] args) {
       SpringApplicationBuilder sab = new SpringApplicationBuilder(DemoSpringbootApplication.class);

        sab.lazyInitialization(true).run(args);
    }
}

上面的写法一和写法二都是我们通过编程方式定制一些 Spring Boot 特性,大多数都是全局特性。包括本文讲述的 “懒加载”。

我们还可以采取更简单的配置文件(application.properties)的方式来配置延迟初始化:

# 默认是关闭的 false
spring.main.lazy-initialization=true

当我们开启了全局的延迟加载后,在 Web 应用程序中将导致许多与 Web 相关的 Bean 直到收到第一次 HTTP 请求后才被初始化。

控制器

/**
 * @author felord.cn
 * @since 2020/3/31 23:31
 */
@RestController
@RequestMapping
public class FooController {
    private FooService fooService;

    public FooController(FooService fooService) {
        System.out.println("fooController init...")
        this.fooService = fooService;
    }

    @GetMapping("/req")
    public Map<String, String> demo() {
        System.out.println("Preparing HTTP request...");
        return fooService.response();
    }

}

服务层

/**
 * @author felord.cn
 * @since 2020/3/31 23:36
 */
@Service
public class FooService {
    public FooService() {
        System.out.println("fooService init ...");
    }

    public Map<String, String> response() {
        Map<String, String> map = new HashMap<>();
        map.put("msg","from fooService");
        return map;
    }
}

调用 /req 接口后我们发现,不单单 FooControllerFooService 在第一次调用初始化,连 Spring MVC 核心 DispatcherServlet 都是第一次调用时初始化。

2.2 局部初始化

如果我们不想让全局延迟初始化作用于个别的 Bean 怎么办?我们可以在这个 Bean 上声明注解 @Lazy(value = false) 即可。你可以改写 2.1 的代码自己试一试。这个 @Lazy 作用于局部,并通过布尔值 value 来控制是否延迟初始化。情况是这样的:

  • 当我们声明全局延迟加载时,@Lazy(value = false)标记的 Bean 会被立即加载。
  • 当我们声明全局延迟加载时,@Lazy 标记的 Bean 会被延迟加载。

请注意:@Lazy 会影响到 @Configuration 下声明的 Bean

3. 注意事项

延迟初始化的缺点是,如果错误配置的 Bean 是延迟初始化的,则在启动期间将不再发生故障,并且只有在初始化 Bean 时错误才会暴露出来,所以一定要经过严格的测试。

同时还必须注意确保 JVM 具有足够的内存来容纳所有应用程序的 Bean,而不仅仅是启动期间初始化的 Bean。因此建议在启用延迟初始化之前先对 JVM 的堆大小进行必要的检测和微调以保证不会溢出。

那些初始化耗时,具有复杂逻辑,而且不是启动的必要选择的 Bean 应当被延迟初始化。

4. 总结

今天对 Spring Boot 如何进行延迟初始化进行了讲解,同时也说明了一些注意事项。间接地也对 Main 方法的几种姿势也进行了展示,希望对你的实际开发有所帮助。关注公众号:Felordcn 将自动获取技术干货资料。

评论系统未开启,无法评论!