Spring中的依赖注入DI源码详细解析

 更新时间:2023年11月30日 09:14:07   作者:啊几  
这篇文章主要介绍了Spring中的依赖注入DI源码详细解析,Spring的依赖注入(Dependency Injection,DI)是Spring框架核心的一部分,它是实现控制反转(Inversion of Control,IoC)的一种方式,需要的朋友可以参考下

前言

Spring的依赖注入(Dependency Injection,DI)是Spring框架核心的一部分,它是实现控制反转(Inversion of Control,IoC)的一种方式。

依赖注入可以帮助我们减少代码的耦合度,提高模块间的独立性和可测试性。

Spring框架的依赖注入主要发生在Spring容器初始化应用上下文时

Spring容器的主要步骤

先说说Spring容器生命周期的一些主要步骤和依赖注入的阶段:

  1. 配置阶段:在这个阶段,Spring容器会读取配置文件(例如XML配置文件或者使用注解的配置类),并根据这些配置信息创建Bean定义。Bean定义包含了创建和装配一个Bean所需要的所有信息。
  2. 实例化阶段:在这个阶段,Spring容器会根据Bean定义创建Bean实例。这通常是通过调用Bean的构造函数或工厂方法来完成的。
  3. 依赖注入阶段:在这个阶段,Spring容器会将依赖注入到已经创建的Bean实例中。这通常是通过调用Bean的setter方法或者通过反射直接设置字段值来完成的。这个阶段也可能会触发更多的Bean的创建和注入,如果被注入的Bean依赖于其他的Bean的话。
  4. 初始化阶段:在这个阶段,Spring容器会调用Bean的初始化方法。这通常是通过调用Bean实现的InitializingBean接口的afterPropertiesSet方法或者调用在配置中指定的自定义初始化方法来完成的。
  5. 使用阶段:在这个阶段,应用代码可以开始使用已经初始化和装配好的Bean了。
  6. 销毁阶段:在这个阶段,当应用上下文被关闭时,Spring容器会调用Bean的销毁方法。这通常是通过调用Bean实现的DisposableBean接口的destroy方法或者调用在配置中指定的自定义销毁方法来完成的。

总的来说,Spring的依赖注入主要发生在Spring容器初始化应用上下文的过程中,具体是在实例化阶段之后,初始化阶段之前的依赖注入阶段。

依赖注入在Spring框架中的触发点

先看看触发依赖注入方法调用堆栈的流程图:

在这里插入图片描述

  1. AbstractApplicationContext#refresh():这个方法是整个Spring应用上下文刷新的入口点,包括Bean的创建和初始化。
  2. AbstractApplicationContext#finishBeanFactoryInitialization(ConfigurableListableBeanFactory): 这个方法会预实例化所有的单例Bean。
  3. DefaultListableBeanFactory#preInstantiateSingletons(): 这个方法会遍历所有的Bean定义,对于每个非懒加载的单例Bean,通过getBean()方法获取Bean的实例。
  4. AbstractBeanFactory#getBean(String): 这个方法会检查是否已经存在一个Bean的实例,如果没有,那么它会触发Bean的创建过程。
  5. AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[]): 这个方法会创建一个新的Bean实例。
  6. AbstractAutowireCapableBeanFactory#doCreateBean(String, RootBeanDefinition, Object[]): 这个方法会创建一个新的Bean实例,填充Bean的属性(也就是依赖注入),并调用Bean的初始化方法。
  7. AbstractAutowireCapableBeanFactory#populateBean(String, RootBeanDefinition, BeanWrapper): 这个方法会进行依赖注入,它会遍历Bean的所有属性,对于每个属性,如果它有一个匹配的Bean定义或者已存在的Bean实例,那么这个Bean会被注入到属性中。

源码解析

下面是依赖注入的主要代码

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		if (bw == null) {
			if (mbd.hasPropertyValues()) {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
			}
			else {
				// Skip property population phase for null instance.
				return;
			}
		}
		// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
		// state of the bean before properties are set. This can be used, for example,
		// to support styles of field injection.
		// 实例化之后,属性设置之前
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
				if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
					return;
				}
			}
		}
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			// MutablePropertyValues是PropertyValues具体的实现类
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// Add property values based on autowire by name if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// Add property values based on autowire by type if applicable.
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
		boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
		PropertyDescriptor[] filteredPds = null;
		if (hasInstAwareBpps) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
			for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
				// 这里会调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,会直接给对象中的属性赋值
				// AutowiredAnnotationBeanPostProcessor内部并不会处理pvs,直接返回了
				PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				if (pvsToUse == null) {
					if (filteredPds == null) {
						filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
					}
					pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
						return;
					}
				}
				pvs = pvsToUse;
			}
		}
		if (needsDepCheck) {
			if (filteredPds == null) {
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}
		// 如果当前Bean中的BeanDefinition中设置了PropertyValues,那么最终将是PropertyValues中的值,覆盖@Autowired
		if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}![在这里插入图片描述](https://img-blog.csdnimg.cn/fbf72b3c614c4efdb5b3ad5bfd6f5a6a.png)
	}

先是判断BeanWrapper是否为空,为空则抛出异常,然后再进行实例化之后的步骤。

也就是执行postProcessAfterInstantiation(Object bean, String beanName)方法

在这里插入图片描述

这里是获取该Bean的自动装配模式,然后基于不同的装配模式添加属性值,Spring的自动装配(autowire)有以下几种模式:

  • no:这是默认的设置,意味着没有自动装配,bean之间的关系需要通过 或 显式配置。
  • byName:Spring容器通过bean的名称自动装配属性。如果一个bean的属性名称与另一个bean的名称相同,那么它们将被自动装配。
  • byType:Spring容器通过类型自动装配属性。如果一个bean的属性类型与另一个bean的类型相同,那么它们将被自动装配。如果有多个相同类型的bean,则会抛出异常。
  • constructor:类似于 byType,但是适用于构造函数。如果容器中存在不止一个与构造函数参数相同类型的bean,则会抛出异常。
  • autodetect:Spring首先尝试通过构造函数自动装配,如果不能通过构造函数自动装配,那么Spring会尝试通过 byType 模式自动装配。

自动装配虽然可以减少配置的复杂性,但也可能引入歧义性。因此,对于大型项目,通常推荐使用显式装配。

在这里插入图片描述

后面会根据缓存的注入点进行注入。injectionMetadataCache就是用来缓存注入点的。

在Spring框架中,injectionMetadataCache是AutowiredAnnotationBeanPostProcessor类(以及它的子类,如CommonAnnotationBeanPostProcessor)的一个成员变量。

在这里插入图片描述

寻找注入点的过程主要发生在AutowiredAnnotationBeanPostProcessor类的findAutowiringMetadata方法中。这个方法首先会尝试从injectionMetadataCache中获取指定类的InjectionMetadata。如果没有找到,就会创建一个新的InjectionMetadata,并将其添加到injectionMetadataCache中。

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;
	}

最主要的是通过类解析注入点的过程,buildAutowiringMetadata方法会通过类解析得到注入点的元数据。

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);
	}

当找到所有注入点就会返回。通过注入点注入主要是InstantiationAwareBeanPostProcessor接口中的PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)方法实现,在具体实现类AutowiredAnnotationBeanPostProcessor中找到了注入点的元数据,就开始注入。

	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		// 找注入点(所有被@Autowired注解了的Field或Method)
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}

注入的元素会有两种,一种是类的字段,还有一种是类的方法,不同类型的元素有不同的注入方法:

这是字段的注入

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {

			Field field = (Field) this.member;
			Object value;
			if (this.cached) {
				// 对于原型Bean,第一次创建的时候,也找注入点,然后进行注入,此时cached为false,注入完了之后cached为true
				// 第二次创建的时候,先找注入点(此时会拿到缓存好的注入点),也就是AutowiredFieldElement对象,此时cache为true,也就进到此处了
				// 注入点内并没有缓存被注入的具体Bean对象,而是beanName,这样就能保证注入到不同的原型Bean对象
				try {
					value = resolvedCachedArgument(beanName, this.cachedFieldValue);
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Unexpected removal of target bean for cached argument -> re-resolve
					value = resolveFieldValue(field, bean, beanName);
				}
			}
			else {
				// 根据filed从BeanFactory中查到的匹配的Bean对象
				value = resolveFieldValue(field, bean, beanName);
			}

			// 反射给filed赋值
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}
		}

这是方法的注入 方法的注入首先会去找到该方法参数的Bean对象,然后利用反射调用该方法

		@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			// 如果pvs中已经有当前注入点的值了,则跳过注入
			if (checkPropertySkipping(pvs)) {
				return;
			}
			Method method = (Method) this.member;
			Object[] arguments;
			if (this.cached) {
				try {
					arguments = resolveCachedArguments(beanName);
				}
				catch (NoSuchBeanDefinitionException ex) {
					// Unexpected removal of target bean for cached argument -> re-resolve
					arguments = resolveMethodArguments(method, bean, beanName);
				}
			}
			else {
				arguments = resolveMethodArguments(method, bean, beanName);
			}
			if (arguments != null) {
				try {
					ReflectionUtils.makeAccessible(method);
					method.invoke(bean, arguments);
				}
				catch (InvocationTargetException ex) {
					throw ex.getTargetException();
				}
			}
		}

到此这篇关于Spring中的依赖注入DI源码详细解析的文章就介绍到这了,更多相关依赖注入DI源码解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解java 中泛型中的类型擦除和桥方法

    详解java 中泛型中的类型擦除和桥方法

    这篇文章主要介绍了详解java 中泛型中的类型擦除和桥方法的相关资料,需要的朋友可以参考下
    2017-06-06
  • IDEA报错"Cannot resolve symbol"问题的解决办法

    IDEA报错"Cannot resolve symbol"问题的解决办法

    早上来了,打开idea发现注解等都变红报错can’t resolvesymbol,由于这个错之前也报过,所以记录一下,这篇文章主要给大家介绍了关于IDEA报错"Cannot resolve symbol"问题的解决办法,需要的朋友可以参考下
    2023-11-11
  • Spring boot详解缓存redis实现定时过期方法

    Spring boot详解缓存redis实现定时过期方法

    本篇文章分享的就是spring boot中的一个轮子,spring cache注解的方式实现接口数据缓存。默认的配置想非常简单,但是有一个弊端是缓存数据为永久缓存,本次将介绍如何设置接口缓存数据的过期时间
    2022-07-07
  • Spring注解@Profile实现开发环境/测试环境/生产环境的切换

    Spring注解@Profile实现开发环境/测试环境/生产环境的切换

    在进行软件开发过程中,一般会将项目分为开发环境,测试环境,生产环境。本文主要介绍了Spring如何通过注解@Profile实现开发环境、测试环境、生产环境的切换,需要的可以参考一下
    2023-04-04
  • Java 实战项目锤炼之在线购书商城系统的实现流程

    Java 实战项目锤炼之在线购书商城系统的实现流程

    读万卷书不如行万里路,只学书上的理论是远远不够的,只有在实战中才能获得能力的提升,本篇文章手把手带你用java+jsp+mysql+servlet+ajax实现一个在线购书商城系统,大家可以在过程中查缺补漏,提升水平
    2021-11-11
  • Java实现Treap树的示例代码

    Java实现Treap树的示例代码

    本文主要介绍了Java实现Treap树的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Java工具类实现高效编写报表

    Java工具类实现高效编写报表

    对于报表数据大部分情况下使用写sql的方式为大屏/报表提供数据来源,但是对于某些复杂情况下仅仅使用sql无法实现,这篇文章主要介绍了Java工具类实现高效编写报表
    2022-11-11
  • Mybatis中where标签与if标签结合使用详细说明

    Mybatis中where标签与if标签结合使用详细说明

    mybatis中if和where用于动态sql的条件拼接,在查询语句中如果缺失某个条件,通过if和where标签可以动态的改变查询条件,下面这篇文章主要给大家介绍了关于Mybatis中where标签与if标签结合使用的详细说明,需要的朋友可以参考下
    2023-03-03
  • Spring Boot获取resources目录下的文件三种方式详解

    Spring Boot获取resources目录下的文件三种方式详解

    在Spring Boot项目中,经常需要获取resources目录下的文件,这些文件可以包括配置文件、模板文件、静态资源等,这篇文章主要介绍了Spring Boot获取resources目录下的文件的三种方式,需要的朋友可以参考下
    2023-06-06
  • Java设计模式中的组合模式

    Java设计模式中的组合模式

    这篇文章主要介绍了Java设计模式中的组合模式,组合模式依据树形结构来组合对象,用来表示部分以及整体层次,种类型的设计模式属于结构型模式
    2022-07-07

最新评论