源码剖析Spring依赖注入:今天你还不会,你就输了

这篇具有很好参考价值的文章主要介绍了源码剖析Spring依赖注入:今天你还不会,你就输了。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在之前的讲解中,我乐意将源码拿出来并粘贴在文章中,让大家看一下。然而,我最近意识到这样做不仅会占用很多篇幅,而且实际作用很小,因为大部分人不会花太多时间去阅读源码。

因此,从今天开始,我将采取以下几个步骤:首先,我会提前画出一张图来展示本章节要讲解的内容的调用链路,供大家参考。其次,在文章中,我只会展示最核心的代码或关键的类。剩下的内容将主要用来讲解原理。如果你真的在学习Spring源码,我希望你能打开你的项目,并跟着我一起深入阅读源码。现在,让我们开始吧。今天的重点是Spring的依赖注入。

基本使用

首先,值得注意的是,在Spring框架中,依赖注入是在bean生成后进行属性赋值的。由于我们的bean通常都是单例模式,所以每个类的属性都必须进行注入。在这个过程中,会涉及到代理、反射等技术的应用。如果你对这些概念不太熟悉的话,建议你提前补充一下相关的前提知识。了解这些基本概念将有助于你更好地理解和掌握Spring框架的依赖注入机制。

首先需要注意的是,尽管图示可能只展示了类之间的简单调用关系,但这并不代表实际的依赖注入过程就是如此简单。实际上,Spring框架的版本和配置方式可能会导致不同的链路调用。然而,无论具体的版本差异如何,Spring框架的依赖注入机制的基本逻辑大致是一样的。

本节课的链路调用图例地址:https://viewer.diagrams.net/index.html?tags={}&highlight=0000ff&edit=_blank&layers=1&nav=1&title=未命名绘图.drawio#Uhttps%3A%2F%2Fraw.githubusercontent.com%2FStudiousXiaoYu%2Fdraw%2Fmain%2F未命名绘图.drawio

Spring的依赖注入有两种方式:手动注入、自动注入。下面我们详细讲解一下这两种方式。

手动注入

在手动注入中,离不开XML配置。有两种常见的方式可以实现手动注入:通过属性和通过构造器。手动就是我们人为控制注入的值,下面是两种配置方式:

<bean id="user" class="com.xiaoyu.service.UserService" >
		<property name="orderService" ref="orderService"/>
</bean>

上面是通过使用set方法进行依赖注入的方式来实现。

<bean id="user" class="com.xiaoyu.service.UserService">
		<constructor-arg index="0" ref="orderService"/>
</bean>

上面是通过使用构造方法进行依赖注入的方式来实现。

自动注入

XML配置

XML也有自动分配的机制,只要不是我们手动指定注入类,那就是自动注入,让我们一起了解如何进行设置。

在XML中,我们可以通过在定义一个Bean时指定自动注入模式来进行优化。这些模式包括byType、byName、constructor、default和no。通过使用这些模式,我们可以更灵活地控制Bean的注入方式。

<bean id="user" class="com.xiaoyu.service.UserService" autowire="byType"/>
<bean id="user" class="com.xiaoyu.service.UserService" autowire="byName"/>

剩下的不举例了,这两种类型,都需要我们的UserService对象有相应的set方法。因为注入的点就是先找到set方法,然后在填充属性之前,Spring会去解析当前类,把当前类的所有方法都解析出来。Spring会解析每个方法,得到对应的PropertyDescriptor对象。PropertyDescriptor对象中包含了几个属性:

name:获取截取后的方法名称:截取规则如下:

  • get开头,则去除get,比如“getXXX”,那么name=XXX(首字母小写),需无参或者第一个参数为int类型
  • is开头不并且返回值为boolean类型,比如“isXXX”,那么name=XXX(首字母小写),需无参
  • set开头并且有无返回值,比如“setXXX”,那么name=XXX(首字母小写),前提是得有入参,如果无入参是解析不到set开头的方法的

readMethodRef:如果是get开头或者is开头的方法,都是readMethodRef,并且存储的引用。

readMethodName:是get开头或者is开头的方法名。包含get/is

writeMethodRef:set开头的方法引用。

writeMethodName:set开头的方法名,包含set。

propertyTypeRef:如果是读方法,则获取的是返回值类型,如果是set写方法,则获取的是入参类型。

具体实现可自行查看源码:java.beans.Introspector#getTargetPropertyInfo()

@Autowired注解

这个注解大家都很熟悉,我简单介绍一下它的基础用法。最后,通过查看源码,我们将依赖注入的过程完整地连起来。

属性注入

基本用法示例:

@Component
public class UserService {
  @Autowired
	public OrderService orderService;
}

setter方法注入

基本用法示例:

@Component
public class UserService {

	public OrderService orderService;
	
	@Autowired
	public void setOrderService(OrderService orderService){
		System.out.println(0);
		this.orderService = orderService;
	}
}

构造器注入

基本用法示例:

@Component
public class UserService {

	public OrderService orderService;
	
	@Autowired
	public UserService(OrderService orderService){
		this.orderService = orderService;
	}
	
}

依赖注入关键源码解析

寻找注入点

在创建一个Bean的过程中,Spring会利用AutowiredAnnotationBeanPostProcessor的postProcessMergedBeanDefinition()方法来找出注入点并进行缓存。具体的找注入点的流程如下:

  1. 如果一个Bean的类型是String,那么则根本不需要进行依赖注入
  2. 遍历目标类中的所有Field字段,field上是否存在@Autowired、@Value、@Inject中的其中一个
  3. static 字段不是注入点,不会进行自动注入
  4. 构造注入点,获取@Autowired中的required属性的值,将字段封装到AutowiredFieldElement对象。
  5. 遍历目标类中的所有Method方法。
  6. method上是否存在@Autowired、@Value、@Inject中的其中一个
  7. static method不是注入点,不会进行自动注入
  8. set方法最好有入参,没有入参或提示日志。
  9. 构造注入点,获取@Autowired中的required属性的值,将方法封装到AutowiredMethodElement对象。
  10. 查看是否还有父类,如果有再次循环直到没有父类。
  11. 将刚才构造好的注入点全都封装到InjectionMetadata,作为当前Bean对于的注入点集合对象,并缓存。

static字段或方法为什么不支持注入

在源码中,Spring会判断字段或方法是否是static来决定是否进行注入。如果字段或方法是static的,Spring不会进行注入操作。这是因为静态字段或方法是属于类的,而不是属于具体的实例。因此,在进行依赖注入时,Spring会注入给具体的实例,而不是整个类。

我们知道Spring是支持创建原型bean的,也就是多例模式。

@Component
@Scope("prototype")
public class UserService {
  @Autowired
  private static OrderService orderService;
  public void test() {
  System.out.println("test123");
  }
}

确实,如果OrderService是prototype类型的,并且Spring支持注入static字段,那么每次注入OrderService到UserService时都会创建一个新的实例。这样做确实违背了static字段的本意,因为static字段是属于类的,而不是实例的。

注入点注入

在依赖注入的过程中,注入点的注入肯定会在populateBean方法中进行属性注入。在这个过程中,会调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,该方法会直接给对象中的属性赋值。这个方法会遍历每个注入点(InjectedElement),并进行依赖注入操作。

源码剖析Spring依赖注入:今天你还不会,你就输了

属性字段注入

  1. 遍历所有AutowiredFieldElement对象。
  2. 将对应的字段封装到DependencyDescriptor。
  3. 调用beanFactory.resolveDependency来获取真正需要注入的bean。
  4. 最后将此次封装的DependencyDescriptor和beanname缓存起来,主要考虑到了原型bean的创建
  5. 利用反射给filed赋值

setter方法注入

  1. 遍历所有AutowiredMethodElement对象。
  2. 调用resolveMethodArguments方法
  3. 遍历每个方法参数,找到匹配的bean对象,将方法对象封装到DependencyDescriptor中。
  4. 调用beanFactory.resolveDependency来获取真正需要注入的bean。
  5. 最后将此次封装的DependencyDescriptor和beanname缓存起来,主要考虑到了原型bean的创建
  6. 利用反射给filed赋值

我们只需要关注findAutowiringMetadata方法的实现,因为大家普遍了解注入的概念。我们主要关注的是它是如何找到注入点的。

	private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
		// Fall back to class name as cache key, for backwards compatibility with custom callers.
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		// Quick check on the concurrent map first, with minimal locking.
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					// 解析注入点并缓存
					metadata = buildAutowiringMetadata(clazz);
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}
	private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
		// 如果一个Bean的类型是String...,那么则根本不需要进行依赖注入
		if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
			return InjectionMetadata.EMPTY;
		}

		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;

		do {
			final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

			// 遍历targetClass中的所有Field
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				// field上是否存在@Autowired、@Value、@Inject中的其中一个
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
					// static filed不是注入点,不会进行自动注入
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}

					// 构造注入点
					boolean required = determineRequiredStatus(ann);
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});

			// 遍历targetClass中的所有Method
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {

				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
				// method上是否存在@Autowired、@Value、@Inject中的其中一个
				MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					// static method不是注入点,不会进行自动注入
					if (Modifier.isStatic(method.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static methods: " + method);
						}
						return;
					}
					// set方法最好有入参
					if (method.getParameterCount() == 0) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation should only be used on methods with parameters: " +
									method);
						}
					}
					boolean required = determineRequiredStatus(ann);
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});

			elements.addAll(0, currElements);
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);

		return InjectionMetadata.forElements(elements, clazz);
	}

@Resource

说到这里,可能有些小伙伴还会使用@Resource注解来进行依赖注入。其实,这和@Autowired注解的逻辑是一样的,只是调用的是其他类的相关方法。具体来说,通过org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition方法来查找注入点,然后在org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties方法中进行属性填充。关于这些细节我们就不详细讨论了,如果感兴趣的话,可以查看一下源码。

@Qualifier

对于使用过@Autowired注解的同学来说,他们肯定也了解@Qualifier注解的作用。@Qualifier主要用于解决一个接口有多个实现类的情况。为了更好地理解,我们来举一个简单的例子:

public interface User {
}
@Component
@Qualifier("userF")
public class UserF implements User{
}
@Component
@Qualifier("userM")
public class UserM implements User{
}

在上述内容中,简要定义了两个实现。现在我们需要使用它们。

@Component
public class UserService {

	@Autowired
	@Qualifier("userM")
	public User user;
}

在这种情况下,会去匹配userM的实体类,而不会出现多个匹配类导致异常。那么它是如何解决这个问题的呢?它是在什么时候找到@Qualifier注解的呢?具体的源码如下所示:

	protected boolean checkQualifier(
			BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {
		// 检查某个Qualifier注解和某个BeanDefinition是否匹配

		// annotation是某个属性或某个方法参数前上所使用的Qualifier
		Class<? extends Annotation> type = annotation.annotationType();
		RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();

		// 首先判断BeanDefinition有没有指定类型的限定符
		AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
		if (qualifier == null) {
			qualifier = bd.getQualifier(ClassUtils.getShortName(type));
		}
		if (qualifier == null) {
			// First, check annotation on qualified element, if any
			Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);
			// Then, check annotation on factory method, if applicable
			if (targetAnnotation == null) {
				targetAnnotation = getFactoryMethodAnnotation(bd, type);
			}
			if (targetAnnotation == null) {
				RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
				if (dbd != null) {
					targetAnnotation = getFactoryMethodAnnotation(dbd, type);
				}
			}
			if (targetAnnotation == null) {
				// Look for matching annotation on the target class
				if (getBeanFactory() != null) {
					try {
						// 拿到某个BeanDefinition对应的类上的@Qualifier
						Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
						if (beanType != null) {
							targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
						}
					}
					catch (NoSuchBeanDefinitionException ex) {
						// Not the usual case - simply forget about the type check...
					}
				}
				if (targetAnnotation == null && bd.hasBeanClass()) {
					targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
				}
			}
			// 注解对象的equals比较特殊,JDK层面用到了动态代理,会比较value
			if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
				return true;
			}
		}
		......
		return true;
	}

他其实是在我们上面所说的属性注入的时候去匹配查找的。具体来说,他会调用beanFactory.resolveDependency方法来获取真正需要注入的bean时进行查找。如果想要查看相关的源码,可以去查看org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#isAutowireCandidate方法。在这个方法中会有更详细的解释。

总结

今天我们主要讲解的是Spring依赖注入。在本文中,我们主要围绕bean填充属性的字段和setter方法展开讨论。要记住的是,在进行属性注入时,我们首先需要找到注入点并进行缓存,然后才会真正进行属性注入。需要注意的是,静态字段或方法是不会进行依赖注入的。最后,我们简单地介绍了一下关键源码,以及对@Resource和@Qualifier进行了简单的分析。如果想要学习Spring源码,一定要结合图例去理解,否则很容易晕头转向。文章来源地址https://www.toymoban.com/news/detail-825466.html

到了这里,关于源码剖析Spring依赖注入:今天你还不会,你就输了的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从源码层面深度剖析Spring循环依赖 | 京东云技术团队

    以下举例皆针对单例模式讨论 图解参考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce 对于单例Bean来说,在Spring容器整个生命周期内,有且只有一个对象。 Spring 在创建 Bean 过程中,使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的: 以 com.gyh.general 包下的 OneBean 为

    2024年02月13日
    浏览(24)
  • 【Spring专题】Spring之Bean的生命周期源码解析——阶段二(二)(IOC之属性填充/依赖注入)

    由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序): Spring底层核心原理解析【学习难度: ★★☆☆☆ 】 手写简易

    2024年02月12日
    浏览(30)
  • Spring IOC:详解【依赖注入数值问题 & 依赖注入方式】

    编译软件:IntelliJ IDEA 2019.2.4 x64 操作系统:win10 x64 位 家庭版 Maven版本:apache-maven-3.6.3 Mybatis版本:3.5.6 spring版本:5.3.1 第一章:初识Spring:如何在Maven工程上搭建Spring框架? 第二章:Spring IOC:IOC在Spring底层中如何实现? 第三章:Spring IOC:详解【依赖注入数值问题 依赖注入

    2024年02月04日
    浏览(36)
  • Spring DI简介及依赖注入方式和依赖注入类型

    目录 一、什么是依赖注入 二、依赖注入方式 1. Setter注入 2. 构造方法注入 3. 自动注入  三、依赖注入类型 1. 注入bean类型 2. 注入基本数据类型 3. 注入List集合 4. 注入Set集合 5. 注入Map集合 6. 注入Properties对象 往期专栏文章相关导读  1. Maven系列专栏文章 2. Mybatis系列专栏文章

    2024年02月02日
    浏览(39)
  • spring——依赖注入原理及注入方式

    🟣1.依赖注入(Dependency Injection,DI) 是一种设计模式和编程技术,其原理是将对象的依赖关系由外部容器来管理和注入。它的目的是解耦组件之间的依赖关系,提高代码的灵活性、可维护性和可测试性。 🟣2.依赖注入的原理 是通过在对象的构造函数、属性或方法中注入所依

    2024年02月08日
    浏览(29)
  • Spring面试整理-Spring的依赖注入

    Spring框架的依赖注入(DI)是其核心功能之一,它允许对象定义它们依赖的其他对象,而不是自己创建或查找它们。这种机制促进了松耦合和更容易的测试。 依赖注入是一种设计模式,其中一个对象或方法提供另一个对象的依赖关系。在Spring中,这些依赖通常是服务、配置值

    2024年01月19日
    浏览(49)
  • Spring:泛型依赖注入

    泛型 :具有占位符(类型参数)的类、结构、接口和方法,通过 的方式定义了一个形式参数,在实例化时再指明具体类型 依赖注入 :IoC 的具体实现,指对象之间的依赖关系在程序运行时由外部容器动态的注入依赖行为方式 泛型依赖注入 :在进行依赖注入的同时,使用泛型

    2024年02月15日
    浏览(27)
  • 4、Spring之依赖注入

    依赖注入就是对类的属性进行赋值 创建名为spring_ioc_xml的新module,过程参考3.1节 注意:constructor-arg标签的数量,必须和某一个构造器方法的参数数量一致 4.4.1.1、配置bean 注意:该property name=\\\"sex\\\" value=\\\"null\\\"写法,实际为sex所赋的值是字符串null 4.4.1.2、测试 由控制台日志可知,

    2024年02月14日
    浏览(23)
  • Spring 的依赖注入

    @ 目录 Spring 的依赖注入 每博一文案 1. 依赖注入 1.1 构造注入 1.1.1 通过参数名进行构造注入 1.1.2 通过参数的下标,进行构造注入 1.1.3 不指定参数下标,不指定参数名字,通过自动装配的方式 1.2 set 注入 2. set注入的各种方式详解 2.1 set 注入外部Bean 2.2 set 注入内部Bean 2.3 set 注

    2024年02月16日
    浏览(39)
  • Spring 依赖注入详解

    目录 1、基于构造器的依赖注入 2、基于 Setter 方法的依赖注入 3、使用构造器注入还是 setter 方法注入? 4、依赖注入解析的过程 5、依赖注入的相关示例 // 依赖关系,指的就是对象之间的相互协作关系         依赖注入(DI)是一个过程,在这个过程中,对象仅通过构造函

    2024年02月06日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包