技术学习实习笔记Java\@Lazy 注解为啥就能破解死循环
lycheeKing
1. 起因:
开发过程中,我发现项目里大量使用了@ManyToOne(fetch = FetchType.LAZY)
注解,脑海中又联想到了@Lazy
,觉得挺有意思记录一下
@ManyToOne(fetch = FetchType.LAZY)
是Hibernate框架中的注解,它表示该属性是多对一关系,而且在查询时应该使用懒加载策略。懒加载是指只有在访问该属性时才会从数据库中加载相关数据。例如,如果一个实体类中有一个ManyToOne关联属性,如果没有使用懒加载策略,那么在查询该实体类时,该ManyToOne关联属性所关联的实体类也会被查询出来,这样就会增加不必要的查询开销和内存占用。而使用懒加载策略,只有在访问该ManyToOne关联属性时才会进行查询,从而提高查询效率和减少内存占用。
@Lazy
是Spring框架中的注解,它可以用于标记Spring Bean的初始化方式。当使用 @Lazy 注解时,Spring容器会在第一次访问该Bean时才进行初始化,而不是在容器启动时就初始化。这样可以提高容器启动速度和减少内存占用。例如,如果一个应用程序中有很多Bean,如果这些Bean都在容器启动时就初始化,那么会增加启动时间和内存占用。而使用 @Lazy 注解,只有在需要使用该Bean时才进行初始化,从而提高容器启动速度和减少内存占用。
虽然这两个注解都与延迟加载有关,但是它们所处的环境不同,使用的目的也不同。@ManyToOne(fetch = FetchType.LAZY)主要用于优化Hibernate查询性能,而@Lazy主要用于优化Spring容器启动性能。
2. @Lazy作用
如果是构造器注入无法自动解决的循环依赖,可以通过添加 @Lazy 注解来解决。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Service public class AService {
BService bService;
@Lazy public AService(BService bService) { this.bService = bService; }
public BService getbService() { return bService; } } @Service public class BService { AService aService; @Lazy public BService(AService aService) { this.aService = aService; }
public AService getaService() { return aService; } }
|
@Lazy 注解可以添加在 AService 或者 BService 的构造方法上,也可以都添加上。
添加上之后,我们再去启动项目,就不会报错了。这样看起来问题解决了,但是其实还是差点意思,看一下启动代码:
1 2 3 4 5 6 7
| ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml"); AService aService = ctx.getBean(AService.class); BService bService = ctx.getBean(BService.class); System.out.println("aService.getClass() = " + aService.getClass()); System.out.println("bService.getClass() = " + bService.getClass()); System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass()); System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());
|
最终打印结果如下:
![图片](../../images/Pictures/@Lazy 注解为啥就能破解死循环/640-1689860582095.png)
从中可以看到,我们从 AService
和 BService
中获取到的 Bean 都是==正常的未被代理的对象==,事实上我们的原始代码确实也没有需要代理的地方。但是,AService 中的 BService 以及 BService 中的 AService 却都是代理对象,按理说 AService 中的 BService 应该和我们从 Spring 容器中获取到的 BService 一致,BService 中的 AService 也应该和 Spring 容器中获取到的 AService 一致,但实际上,两者却并不相同。
不过这样也好懂了,为什么 Spring 能把一个死结给解开,就是因为 AService 和 BService 各自注入的 Bean 都不是原始的 Bean,都是一个代理的 Bean,AService 中注入的 BService 是一个代理对象,同理,BService 中注入的 AService 也是一个代理对象。
这也是为什么我一开始说这个问题 Spring
解决了又没解决。
其实,这就是 @Lazy 这个注解的工作原理,看名字,加了该注解的对象会被延迟加载,实际上被该注解标记的对象,会自动生成一个代理对象。
另外两个问题,也可以通过 @Lazy 注解来解决,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Service @Scope("prototype") public class AService { @Lazy @Autowired BService bService;
} @Service @Scope("prototype") public class BService { @Lazy @Autowired AService aService; }
|
这里 @Lazy 只要一个其实就能解决问题,也可以两个都添加。
对于含有 @Async 注解的情况,也可以通过 @Lazy 注解来解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Service public class AService { @Autowired @Lazy BService bService;
@Async public void hello() { bService.hello(); }
public BService getbService() { return bService; } } @Service public class BService { @Autowired AService aService;
public void hello() { System.out.println("xxx"); }
public AService getaService() { return aService; } }
|
如此,循环依赖可破!
总而言之一句话,@Lazy 注解是通过建立一个中间代理层,来破解循环依赖的。
3. 原理分析
接下来再来分析一下 @Lazy 注解处理的源码。
这块的源码分析就不从头开始分析了,可以参考 @Autowired 到底是怎么把变量注入进来的?一文。这里就借用该文的总结,稍微回顾一下属性注入的过程:
在创建 Bean 的时候,原始 Bean 创建出来之后,会调用 populateBean
方法进行 Bean 的属性填充。
接下来调用 postProcessAfterInstantiation
方法去判断是否需要执行后置处理器,如果不需要,就直接返回了。
调用 postProcessProperties
方法,去触发各种后置处理器的执行。
在第 3 步的方法中,调用 findAutowiringMetadata
,这个方法又会进一步触发 buildAutorwiringMetadata
方法,去找到包
含了 @Autowired、@Value 以及 @Inject 注解的属性或者方法,并将之封装为 InjectedElement
返回。
调用 InjectedElement#inject 方法进行属性注入。
![](../../images/Pictures/@Lazy 注解为啥就能破解死循环/640.png)
接下来执行 resolvedCachedArgument
方法尝试从缓存中找到需要的 Bean 对象。
如果缓存中不存在,则调用 resolveFieldValue
方法去容器中找到 Bean。
最后调用 makeAccessible
和 set 方法完成属性的赋值。
在第 7 步中,调用 resolveFieldValue
方法去解析 Bean,@Lazy 注解的相关逻辑就是在这个方法中进行处理的(对应 @Autowired 到底是怎么把变量注入进来的?一文的 3.2 小节)。
resolveFieldValue
方法最终会执行到 resolveDependency
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Nullable public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); if (Optional.class == descriptor.getDependencyType()) { return createOptionalDependency(descriptor, requestingBeanName); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { return new DependencyObjectProvider(descriptor, requestingBeanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName); } else { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }
|
在这个方法中,首先会判断注入的属性类型是 Optional、ObjectFactory 还是 JSR-330 中的注解,我们这里都不是,所以走最后一个分支。
在最后一个 else 中,首先调用 getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary 方法看一下是否需要延迟加载 Bean 对象,@Lazy 注解就是在这里进行处理的。如果能够延迟加载,那么该方法的返回值就不为 null,就可以直接返回了,就不需要执行 doResolveDependency 方法了。
ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary:
1 2 3 4 5
| @Override @Nullable public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); }
|
可以发现,这个方法首先会调用 isLazy 去判断一下是否需要延迟加载,如果需要,则调用 buildLazyResolutionProxy 方法构建一个延迟加载的对象;如果不需要,则直接返回一个 null 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| protected boolean isLazy(DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; }
|
这个判断方法主要是检查当前类中各种参数上是否含有 @Lazy 注解、方法、属性以及类名上是否含有 @Lazy 注解,如果有,则返回 true,否则返回 false。
再来看 buildLazyResolutionProxy 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| private Object buildLazyResolutionProxy( final DependencyDescriptor descriptor, final @Nullable String beanName, boolean classOnly) { BeanFactory beanFactory = getBeanFactory(); final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory; TargetSource ts = new TargetSource() { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic() { return false; } @Override public Object getTarget() { Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null); Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null); if (target == null) { Class<?> type = getTargetClass(); if (Map.class == type) { return Collections.emptyMap(); } else if (List.class == type) { return Collections.emptyList(); } else if (Set.class == type || Collection.class == type) { return Collections.emptySet(); } throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(), "Optional dependency not present for lazy injection point"); } if (autowiredBeanNames != null) { for (String autowiredBeanName : autowiredBeanNames) { if (dlbf.containsBean(autowiredBeanName)) { dlbf.registerDependentBean(autowiredBeanName, beanName); } } } return target; } @Override public void releaseTarget(Object target) { } }; ProxyFactory pf = new ProxyFactory(); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } ClassLoader classLoader = dlbf.getBeanClassLoader(); return (classOnly ? pf.getProxyClass(classLoader) : pf.getProxy(classLoader)); }
|
这个方法就是用来生成代理的对象的,这里构建了代理对象 TargetSource,在其 getTarget 方法中,会去执行 doResolveDependency 获取到被代理的对象(doResolveDependency 的获取逻辑可以参考 @Autowired 到底是怎么把变量注入进来的?一文),而 getTarget 方法只有在需要的时候才会被调用。所以,@Lazy 注解所做的事情,就是在给 Bean 中的各个属性注入值的时候,原本需要去 Spring 容器中找注入的对象,现在不找了,先给一个代理对象顶着,需要的时候再去 Spring 容器中查找。