环境:Spring6.1.8
1. 简介
Spring中的@Lazy注解主要用于实现惰性加载(延迟加载),它可以应用在类、方法、构造方法、参数和字段上。以下是@Lazy注解的作用、应用场景。
1.1 作用
在Spring框架中,默认情况下,所有的Bean在容器启动时都会被初始化。但是,有些Bean的初始化可能涉及到一些重量级的操作,如网络IO操作、复杂计算等,这些操作会消耗大量的系统资源。通过使用@Lazy注解,可以让这些Bean在真正需要时才进行初始化,从而提高系统的启动速度和性能。
1.2 应用场景
- 提升系统启动速度:当应用包含大量的Bean,如果存在某些Bean初始化操作非常耗时(如网络IO操作或复杂耗时计算),通过@Lazy注解可以显著提升系统的启动速度。如:应用启动时,需要从Redis读取大量的缓存数据,如果将此Bean使用@Lazy标注,那么应用启动会非常快,而当在使用缓存服务时才去读取redis初始化数据。
- 解决循环依赖:如果两个Bean之间存在循环依赖,即A依赖B,B又依赖A(构造函数注入),这会导致Spring容器在初始化这些Bean时陷入死循环。使用@Lazy注解可以解决这类问题。
- 单例Bean正确注入多例Bean:如果A是单例,B是多例,在A中注入B实例,要想正确的注入(每次使用B时都是新对象)通过使用@Lazy能够轻松解决。
以上是关于@Lazy注解的简介及应用场景,接下来将详细介绍@Lazy的5种使用方式。
2. 实战案例
2.1 环境准备
public class PersonDAO {
}
public class PersonService {
private PersonDAO dao ;
public String toString() {
return "PersonService [dao=" + dao.getClass() + "]";
}
}
// 测试入口代码
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(PersonDAO.class) ;
context.register(PersonService.class) ;
context.refresh() ;
System.out.println(context.getBean(PersonService.class)) ;
}
接下的每个示例都将基于上面的类进行。
2.2 字段注入
// @Resource
// @Autowired
@Lazy
private PersonDAO dao ;
输出结果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
通过@Lazy标注的字段,最终注入的是代理类(不管上面使用的@Resource还是@Autowired)。
注:在上面测试入口代码中,我们使用的是AnnotationConfigApplicationContext,如果你使用的是GenericApplicationContext那么在默认情况下@Autowired是不会生效的,这时候你还需要做如下设置:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory() ;
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()) ;
将此BeanFactory设置到ApplicationContext中即可。
2.3 方法注入
@Resource
@Lazy
public void setPersonDAO(PersonDAO dao) {
this.dao = dao ;
}
输出结果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
同样的生成了代理。
方法注入,你还可以将@Lazy放到方法参数上,如下示例:
@Resource
public void setPersonDAO(@Lazy PersonDAO dao) {
this.dao = dao ;
}
这种方式也是会被生成代理对象。
2.4 构造函数注入
@Lazy
public PersonService(PersonDAO dao) {
this.dao = dao ;
}
输出结果
PersonService [dao=class com.pack.PersonDAO$$SpringCGLIB$$0]
同样,注解也可以使用在参数上
public PersonService(@Lazy PersonDAO dao) {
this.dao = dao ;
}
构造函数注入与方法注入基本一致。
2.5 单例Bean注入多例Bean
修改PersonDAO;
@Scope("prototype")
public class PersonDAO {
}
通过@Scope将其声明为多例。
修改PersonService随意添加一个方法。
public class PersonService {
@Autowired
private PersonDAO dao ;
public void save() {
System.out.printf("PersonDAO hashCode: %s%n", dao) ;
}
}
测试类:
PersonService ps = context.getBean(PersonService.class);
ps.save() ;
ps.save() ;
ps.save() ;
当dao字段上不添加@Lazy注解时,输出结果:
PersonDAO hashCode: com.pack.PersonDAO@66565121
PersonDAO hashCode: com.pack.PersonDAO@66565121
PersonDAO hashCode: com.pack.PersonDAO@66565121
每次都是同一个对象,这不是我们期望的结果
dao字段添加@Lazy注解后,再次运行
PersonDAO hashCode: com.pack.PersonDAO@73a2e526
PersonDAO hashCode: com.pack.PersonDAO@13f95696
PersonDAO hashCode: com.pack.PersonDAO@68be8808
正确的输出结果,每次使用都是不同的实例。
2.6 循环依赖
class class A {
private B b ;
public A(B b) {
this.b = b ;
}
}
public class B {
private A a ;
public B(A a) {
this.a = a ;
}
}
上面的依赖通过构造方法注入,这种情况下容器启动是会报错的,如下:
图片
出现循环依赖错误,通过@Lazy注解解决此问题,只需要在任意类的构造函数上使用@Lazy注解,如下:
public A(@Lazy B b) {
this.b = b ;
}
只需要在其中一方加入了@Lazy注解后,问题得到解决。