Java Spring @Lazy延迟注入源码案例详解

 更新时间:2021年09月02日 09:28:51   作者:liangsheng_g  
这篇文章主要介绍了Java Spring @Lazy延迟注入源码案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

前言

有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因

一、一个简单的小例子

代码如下:

@Service
public class NormalService1 {

	@Autowired
	@Lazy
	private MyService myService;

	public void doSomething() {
		myService.getName();
	}
}

作用是为了进行延迟加载,在NormalService1进行属性注入的时候,如果MyService还没有生成bean也不用担心,会注入一个代理,但是在实际运行的时候,会获取Spring容器中实际的MyService,在某些情况下,因为spring生命周期的原因,这个注解有大用。

二、源码解读

1. 注入

代码如下(DefaultListableBeanFactory#resolveDependency):

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 {
			//如果注入属性添加了@Lazy,懒加载,此时spring会根据具体类型搞个cglib代理类
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			if (result == null) {
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

很明显要执行getLazyResolutionProxyIfNecessary方法,如果加了@Lazy注解,最终会执行buildLazyResolutionProxy方法

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
		Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,
				"BeanFactory needs to be a DefaultListableBeanFactory");
		final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
		TargetSource ts = new TargetSource() {
			@Override
			public Class<?> getTargetClass() {
				return descriptor.getDependencyType();
			}
			@Override
			public boolean isStatic() {
				return false;
			}
			@Override
			public Object getTarget() {
				Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
				/**
				something valid
				**/
				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);
		}
		return pf.getProxy(beanFactory.getBeanClassLoader());
	}

可以看到上面这段代码,其实就是生成了一个TargetSource,然后再生成了一个代理(CGLIB或者JDK),然后作为MyService对象注入给了NormalService1。那么所谓的执行的过程中才进行获取真正的MyService对象是什么意思呢?

2. 使用逻辑

本文示例代码使用的是CGLIB代理,其实是类似的,因为注入的MyService是个CGLIB代理对象,那么在执行方法的时候,就会调用CglibAopProxy#DynamicAdvisedInterceptor#intercept方法

在这里插入图片描述

那么此处其实调用的就是上面的

Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);

这个方法就不用认真看了,主要功能就是从Spring容器中找到MyService。
在之前讲@Autowired原理和@Resource注入原理的时候解释过了,不清楚的可以看专栏里其他文章。
拿出来之后会发现,咱们拿到的target对象还是一个CGLIB代理的对象

在这里插入图片描述

那么当执行方法逻辑时

在这里插入图片描述

由于target是CGLIB对象,会再次进入到CglibAopProxy#DynamicAdvisedInterceptor#intercept方法。
此时拿到的target对象类型就不同了

在这里插入图片描述

是我们代理之前的target对象,此时再次进行invoke的时候,就会进行动态代理的一般逻辑,先查找该方法匹配的所有advice,然后依次调用,最终调用target本身对于方法的执行。

总结

所以可以发现其实@Lazy只不过是给spring的代理对象proxy再进行了一次proxy,只不过没有在注入的时候,就获取到对象,而是借用了方法invoke时通过proxy的intercept方法getTarget,然后进行方法调用,延迟了对象的注入。之后每次调用的时候都需要从Spring容器中获取到原生的proxy对象。

在这里插入图片描述

到此这篇关于Java Spring @Lazy延迟注入源码案例详解的文章就介绍到这了,更多相关Java Spring @Lazy延迟注入源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot项目中通过@Value给参数赋值失败的解决方案

    SpringBoot项目中通过@Value给参数赋值失败的解决方案

    springboot项目中通过@Value给属性附值失败,给参数赋值失败,打印为空值,文中通过代码示例给大家介绍的非常详细,对大家解决问题有一定的帮助,需要的朋友可以参考下
    2024-04-04
  • 解决Maven项目本地公共common包缓存问题

    解决Maven项目本地公共common包缓存问题

    这篇文章主要介绍了解决Maven项目本地公共common包缓存问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 解决@RequestMapping和@FeignClient放在同一个接口上遇到的坑

    解决@RequestMapping和@FeignClient放在同一个接口上遇到的坑

    这篇文章主要介绍了解决@RequestMapping和@FeignClient放在同一个接口上遇到的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • Layui 后台加载菜单栏名称以及url的例子

    Layui 后台加载菜单栏名称以及url的例子

    今天小编就为大家分享一篇Layui 后台加载菜单栏名称以及url的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • Java编程实现汉字按字母顺序排序的方法示例

    Java编程实现汉字按字母顺序排序的方法示例

    这篇文章主要介绍了Java编程实现汉字按字母顺序排序的方法,结合具体实例形式分析了java编码转换及字母排序相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • 详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    详解Spring Cloud Finchley版中Consul多实例注册的问题处理

    这篇文章主要介绍了详解Spring Cloud Finchley版中Consul多实例注册的问题处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • SpringBoot 将配置文件挂到 jar 包外面的操作方法

    SpringBoot 将配置文件挂到 jar 包外面的操作方法

    在 SpringBoot 中,可以将配置文件放在 jar 包外面,这样可以方便地修改配置而不需要重新打包和部署,这篇文章主要介绍了SpringBoot 如何将配置文件挂到 jar 包外面,需要的朋友可以参考下
    2023-03-03
  • java中List接口的方法详解

    java中List接口的方法详解

    这篇文章主要介绍了java中List接口的方法详解,List接口是继承Collection接口,所以Collection集合中有的方法,List集合也继承过来,本文主要介绍一下list下的方法,需要的朋友可以参考下
    2023-10-10
  • Java 十大排序算法之希尔排序刨析

    Java 十大排序算法之希尔排序刨析

    希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。本文会以图解的方式详细介绍希尔排序的基本思想及其代码实现
    2021-11-11
  • java 设计模型之单例模式详解

    java 设计模型之单例模式详解

    本文主要介绍了java 单例模式,单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在,希望能帮助有需要的同学
    2016-07-07

最新评论