@Lazy 能把启动时间省多少、代价挪到了哪里?配置错误还能在启动时发现吗?注入点上的 @Lazy 和 Bean 上的 @Lazy 有什么区别?带 @EventListener 的 Bean 能不能懒加载?这篇在 Spring Framework 7.0.9 上逐项验证。
饿汉 10 个 Heavy(构造器各睡 20 ms):refresh() 312 ms;之后第一次取其中 5 个 0 ms;再取这 5 个 0 ms;已创建的单例数 23 懒加载 10 个 Heavy(构造器各睡 20 ms):refresh() 65 ms;之后第一次取其中 5 个 124 ms;再取这 5 个 0 ms;已创建的单例数 18 饿汉 50 个 Heavy(构造器各睡 20 ms):refresh() 1263 ms;之后第一次取其中 5 个 0 ms;再取这 5 个 0 ms;已创建的单例数 63 懒加载 50 个 Heavy(构造器各睡 20 ms):refresh() 86 ms;之后第一次取其中 5 个 127 ms;再取这 5 个 0 ms;已创建的单例数 18
懒加载把构造代价从 refresh 挪到第一次使用,总工作量不变。
eager:refresh() 就失败了:UnsatisfiedDependencyException
lazy:refresh() 成功(没有发现 Missing 缺失)
第一次 getBean("fails") 才失败:UnsatisfiedDependencyException: No qualifying bean of type 'Missing' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}依赖缺失这类错误在懒加载下要到第一次使用才抛出。
--- 场景 a 1. 构造 Heavy(目标 Bean 是普通单例) 2. 构造 User(heavy 字段是 @Lazy 注入) 3. --- refresh 完成,调用 use() 4. User 第一次调用 heavy.hi() 之前 5. User 调用完 heavy.hi() 6. heavy 字段的类型:Main$Heavy$$…(CGLIB 代理) --- 场景 b 1. 构造 User(heavy 字段是 @Lazy 注入) 2. --- refresh 完成,调用 use() 3. User 第一次调用 heavy.hi() 之前 4. 构造 Heavy(目标 Bean 标了 @Lazy) 5. User 调用完 heavy.hi() 6. heavy 字段的类型:Main$Heavy$$…(CGLIB 代理) --- 场景 c 1. 构造 UserPlain(heavy 字段是普通 @Autowired) 2. 构造 Heavy(目标 Bean 标了 @Lazy) 3. --- refresh 完成,调用 use() 4. UserPlain 第一次调用 heavy.hi() 之前 5. UserPlain 调用完 heavy.hi()
只推迟解析,目标和注入点两处都 @Lazy 才推迟创建。
--- 监听器 Bean 的懒加载情况:eager 1. 构造 Lis(带 @EventListener 方法) 2. Lis 收到 ContextRefreshedEvent 3. --- refresh 完成,现在发布自定义事件 4. Lis 收到自定义事件 E1 --- 监听器 Bean 的懒加载情况:lazy 1. 构造 Lis(带 @EventListener 方法) 2. Lis 收到 ContextRefreshedEvent 3. --- refresh 完成,现在发布自定义事件 4. Lis 收到自定义事件 E1 --- 监听器 Bean 的懒加载情况:all 1. 构造 Lis(带 @EventListener 方法) 2. Lis 收到 ContextRefreshedEvent 3. --- refresh 完成,现在发布自定义事件 4. Lis 收到自定义事件 E1
带 @EventListener 的 Bean 不会被推迟。
SpringApplication.setLazyInitialization(false):启动后单例 22 个;HeavyBean 已构造?true SpringApplication.setLazyInitialization(true):启动后单例 20 个;HeavyBean 已构造?false
SpringApplication.setLazyInitialization(true) 一次性让所有 Bean 懒加载。