Spring 源码解读:实现依赖注入的构造函数与Setter注入

2024-08-26 05:36

本文主要是介绍Spring 源码解读:实现依赖注入的构造函数与Setter注入,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言

依赖注入(Dependency Injection)是Spring框架的核心特性之一,它通过将对象的依赖交由IoC容器管理,帮助开发者实现松耦合的代码结构。Spring支持多种依赖注入方式,其中最常见的是构造函数注入和Setter方法注入。本篇文章将通过一个完整的自定义IoC容器案例详细演示这两种注入方式,并进行深入的Spring 5.x源码解读。

第一部分:构造函数注入

1.1 构造函数注入的基本概念

构造函数注入是一种在对象创建时,通过构造函数将依赖传递给对象的方式。这种方式在对象创建时即保证了依赖的不可变性,适用于强制性依赖的场景。

1.2 自定义实现构造函数注入

我们将设计一个简单的IoC容器,支持通过构造函数注入依赖。

代码实现

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;// 定义Service接口,供Controller类依赖
interface Service {void execute();
}// Service接口的具体实现类
class ServiceImpl implements Service {@Overridepublic void execute() {System.out.println("Service is executing...");}
}// Controller类依赖于Service接口
class Controller {private final Service service;// 通过构造函数注入Service实例public Controller(Service service) {this.service = service;}// 调用Service的方法public void doSomething() {service.execute();}
}// 自定义的简单IoC容器,用于管理对象创建和依赖注入
public class SimpleIoCContainer {// 使用Map来存储Bean实例,key为类类型,value为对应的实例private Map<Class<?>, Object> beanMap = new HashMap<>();// 注册Bean,处理构造函数注入public void registerBean(Class<?> beanClass) {try {// 获取类的构造函数(假设每个类只有一个构造函数)Constructor<?> constructor = beanClass.getConstructors()[0];// 获取构造函数的参数类型Class<?>[] parameterTypes = constructor.getParameterTypes();Object[] parameters = new Object[parameterTypes.length];// 递归处理依赖的实例化for (int i = 0; i < parameterTypes.length; i++) {parameters[i] = getBean(parameterTypes[i]);}// 使用构造函数创建对象实例Object bean = constructor.newInstance(parameters);// 将创建的实例注册到容器中beanMap.put(beanClass, bean);} catch (Exception e) {throw new RuntimeException("Failed to register bean: " + beanClass.getName(), e);}}// 获取Bean实例public <T> T getBean(Class<T> beanClass) {// 如果容器中还没有这个类型的实例,就先注册一个if (!beanMap.containsKey(beanClass)) {registerBean(beanClass);}// 返回已注册的实例return beanClass.cast(beanMap.get(beanClass));}public static void main(String[] args) {// 创建IoC容器SimpleIoCContainer container = new SimpleIoCContainer();// 获取Controller实例,依赖自动注入Controller controller = container.getBean(Controller.class);// 使用Controllercontroller.doSomething();}
}

详细解释:

  • SimpleIoCContainer通过反射机制处理构造函数注入。registerBean()方法负责根据构造函数参数类型递归创建对象并注册到容器中。
  • getBean()方法用于获取已注册的Bean实例。如果Bean未注册,容器会自动注册并创建该Bean。
  • main()方法展示了如何使用这个IoC容器来管理Controller和它的依赖Service
1.3 构造函数注入的类图和流程图
类图
SimpleIoCContainer
-Map<Class, Object> beanMap
+void registerBean(Class beanClass)
+T getBean(Class<T> beanClass)
«interface»
Service
+void execute()
ServiceImpl
+void execute()
Controller
-Service service
+Controller(Service service)
+void doSomething()

解释

  • SimpleIoCContainer类管理了所有的Bean创建和注入过程。
  • ServiceController是典型的依赖关系,展示了如何通过构造函数注入依赖。
流程图
SimpleIoCContainer.getBean
检查Bean是否已注册
获取Controller的构造函数
递归获取Service依赖
调用Controller构造函数,传入Service实例
返回初始化的Controller实例

解释

  • 流程图展示了容器如何通过反射机制处理构造函数注入。
  • 从检查Bean是否已注册,到递归处理依赖,最后返回初始化的实例,这些步骤展示了构造函数注入的全流程。
1.4 Spring源码解读:构造函数注入

在Spring Framework 5.x中,构造函数注入是通过ConstructorResolver类来处理的。这个类的核心任务是根据配置选择合适的构造函数,并通过反射机制实例化对象。

autowireConstructor 方法
public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor<?>[] chosenCtors, Object[] explicitArgs) {BeanWrapperImpl bw = new BeanWrapperImpl();// 初始化BeanWrapper,设置相应的转换器this.beanFactory.initBeanWrapper(bw);Constructor<?> constructorToUse = null;ArgumentsHolder argsHolderToUse = null;Object[] argsToUse = null;// 如果有显式参数,直接使用这些参数if (explicitArgs != null) {argsToUse = explicitArgs;} else {// 否则,解析构造函数参数ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();int minNrOfArgs = cargs.getArgumentCount();// 遍历所有构造函数,寻找最合适的一个for (Constructor<?> candidate : chosenCtors) {Class<?>[] paramTypes = candidate.getParameterTypes();if (paramTypes.length >= minNrOfArgs) {ArgumentsHolder argsHolder = createArgumentArray(beanName, mbd, bw, paramTypes);if (argsHolder != null) {argsHolderToUse = argsHolder;constructorToUse = candidate;argsToUse = argsHolder.arguments;break;}}}}// 通过反射调用构造函数,创建Bean实例Object beanInstance = bw.instantiate(constructorToUse, argsToUse);return bw;
}

详细解读:

  • autowireConstructor 方法:这个方法负责自动选择和调用Bean的构造函数。它首先初始化一个BeanWrapperImpl实例,这是Spring用于包装Bean对象的工具类。
  • 参数解析:如果没有显式传递参数,Spring会根据Bean定义中的配置解析构造函数参数,createArgumentArray方法在此过程中起到了关键作用,它会根据构造函数的参数类型,匹配相应的依赖。
  • 构造函数选择:Spring会遍历所有可用的构造函数,并选择最匹配的一个。这一过程的核心在于对每个构造函数参数类型的匹配与转换。
  • 实例化:最终,通过反射调用选定的构造函数,完成Bean的实例化。
createArgumentArray 方法
private ArgumentsHolder createArgumentArray(String beanName, RootBeanDefinition mbd, BeanWrapper bw, Class<?>[] paramTypes) {ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);// 依次处理每个参数for (int i = 0; i < paramTypes.length; i++) {MethodParameter methodParam = new MethodParameter(paramTypes[i], i);// 从容器中解析依赖,处理类型转换Object argValue = resolveDependency(methodParam, beanName);args.arguments[i] = bw.convertForProperty(argValue, paramTypes[i]);}return args;
}

**详细

解读**:

  • createArgumentArray 方法:负责为构造函数的每个参数创建对应的值。它会调用resolveDependency方法从Spring容器中查找并注入合适的依赖。
  • 依赖解析:Spring会根据参数的类型和位置,找到匹配的依赖对象,进行注入。这也是Spring IoC容器的核心功能之一,即自动装配(autowiring)。
  • 类型转换convertForProperty 方法负责将找到的依赖对象转换为构造函数所需要的类型。这使得Spring能够在构造函数参数类型与实际Bean类型之间进行灵活的匹配。

第二部分:Setter方法注入

2.1 Setter方法注入的基本概念

Setter方法注入允许在对象创建后,通过Setter方法动态地注入依赖。这种方式提供了更大的灵活性,适用于那些可能需要更改或后期注入依赖的对象。

2.2 自定义实现Setter方法注入

我们将扩展前面的IoC容器,实现对Setter方法注入的支持。

代码实现

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;// 定义一个简单的注解,用于标记需要依赖注入的字段
@interface Autowired {}// Service接口,供Controller类依赖
interface Service {void execute();
}// Service接口的具体实现类
class ServiceImpl implements Service {@Overridepublic void execute() {System.out.println("Service is executing...");}
}// AdvancedService接口,供Controller类可选依赖
interface AdvancedService {void performAdvancedTask();
}// AdvancedService接口的具体实现类
class AdvancedServiceImpl implements AdvancedService {@Overridepublic void performAdvancedTask() {System.out.println("Advanced service is performing a task...");}
}// Controller类,依赖于Service和AdvancedService
class Controller {private final Service service;// 使用@Autowired注解标记需要通过Setter方法注入的依赖@Autowiredprivate AdvancedService advancedService;// 构造函数注入Service实例public Controller(Service service) {this.service = service;}// 调用依赖的方法public void doSomething() {service.execute();if (advancedService != null) {advancedService.performAdvancedTask();} else {System.out.println("No advanced service available.");}}
}// 自定义的IoC容器,扩展了对Setter方法注入的支持
public class SimpleIoCContainer {// 使用Map来存储Bean实例,key为类类型,value为对应的实例private Map<Class<?>, Object> beanMap = new HashMap<>();// 注册Bean,处理构造函数注入和Setter方法注入public void registerBean(Class<?> beanClass) {try {// 获取类的构造函数(假设每个类只有一个构造函数)Constructor<?> constructor = beanClass.getConstructors()[0];// 获取构造函数的参数类型Class<?>[] parameterTypes = constructor.getParameterTypes();Object[] parameters = new Object[parameterTypes.length];// 递归处理依赖的实例化for (int i = 0; i < parameterTypes.length; i++) {parameters[i] = getBean(parameterTypes[i]);}// 使用构造函数创建对象实例Object bean = constructor.newInstance(parameters);// 将创建的实例注册到容器中beanMap.put(beanClass, bean);// 处理通过@Autowired注解标记的字段的依赖注入injectDependencies(bean);} catch (Exception e) {throw new RuntimeException("Failed to register bean: " + beanClass.getName(), e);}}// 处理通过Setter方法进行的依赖注入private void injectDependencies(Object bean) {Field[] fields = bean.getClass().getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(Autowired.class)) {field.setAccessible(true);try {// 获取依赖的实例Object dependency = getBean(field.getType());// 通过反射将依赖注入到字段中field.set(bean, dependency);} catch (IllegalAccessException e) {throw new RuntimeException("Failed to inject dependencies for bean: " + bean.getClass().getName(), e);}}}}// 获取Bean实例public <T> T getBean(Class<T> beanClass) {// 如果容器中还没有这个类型的实例,就先注册一个if (!beanMap.containsKey(beanClass)) {registerBean(beanClass);}// 返回已注册的实例return beanClass.cast(beanMap.get(beanClass));}public static void main(String[] args) {// 创建IoC容器SimpleIoCContainer container = new SimpleIoCContainer();// 获取Controller实例,依赖自动注入Controller controller = container.getBean(Controller.class);// 使用Controllercontroller.doSomething();}
}

详细解释:

  • SimpleIoCContainer扩展了功能,injectDependencies()方法负责处理@Autowired注解的字段,通过Setter方法注入依赖。
  • AdvancedServiceAdvancedServiceImpl被引入,用于展示Setter方法注入的实现。
2.3 Setter方法注入的类图和流程图
类图
SimpleIoCContainer
-Map<Class, Object> beanMap
+void registerBean(Class beanClass)
+T getBean(Class<T> beanClass)
-void injectDependencies(Object bean)
«interface»
Service
+void execute()
«interface»
AdvancedService
+void performAdvancedTask()
ServiceImpl
+void execute()
AdvancedServiceImpl
+void performAdvancedTask()
Controller
-Service service
-AdvancedService advancedService
+Controller(Service service)
+void doSomething()
流程图
SimpleIoCContainer.getBean
检查Bean是否已注册
获取Controller的构造函数
递归获取Service依赖
调用Controller构造函数,传入Service实例
处理 Autowired注解的字段
递归获取AdvancedService依赖
通过反射注入AdvancedService实例到Controller
返回初始化的Controller实例

解释

  • 流程图展示了容器如何通过反射机制处理Setter方法注入。
  • 包括如何递归获取依赖并通过反射注入到字段中。
2.4 Spring源码解读:Setter方法注入

在Spring 5.x中,Setter方法注入是通过AutowiredAnnotationBeanPostProcessor类来实现的,该类会在Bean初始化的过程中扫描并注入被@Autowired注解标注的依赖。

InjectedElement.inject 方法

InjectedElement 类是 Spring 框架中用于处理依赖注入的一个核心类,它的 inject 方法负责将依赖注入到目标对象中。对于 @Autowired 注解的字段,该方法最终会通过反射调用相应的 Setter 方法来进行注入。

protected void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {if (this.isField) {// 如果是字段注入,通过反射将值直接设置到字段中Field field = (Field) this.member;Object value = this.resolveFieldValue(field, target, beanName);ReflectionUtils.makeAccessible(field);field.set(target, value);} else {// 如果是方法(通常是Setter方法),调用方法注入依赖Method method = (Method) this.member;Object[] arguments = this.resolveMethodArguments(method, target, beanName);if (arguments != null) {ReflectionUtils.makeAccessible(method);method.invoke(target, arguments);}}
}

详细解读:

  • this.isField:该判断用于确定当前注入的元素是字段还是方法。如果是字段,则直接通过反射设置字段的值;如果是方法(通常为 Setter 方法),则调用方法进行注入。

  • 字段注入:当 isFieldtrue 时,Spring 将通过 ReflectionUtils.makeAccessible 将字段设为可访问,然后使用反射将解析出的依赖值注入到目标字段中。

  • 方法注入(Setter 注入):当 isFieldfalse 时,Spring 将认为需要通过方法注入依赖。通常,这意味着

调用目标对象的 Setter 方法。Spring 首先解析方法的参数,然后通过 method.invoke(target, arguments) 调用实际的 Setter 方法,将解析出的依赖注入目标对象。

resolveMethodArguments 方法

这个方法是 InjectedElement 类中用于解析方法参数的关键方法。

protected Object[] resolveMethodArguments(Method method, Object target, String beanName) {Class<?>[] paramTypes = method.getParameterTypes();Object[] arguments = new Object[paramTypes.length];for (int i = 0; i < paramTypes.length; i++) {arguments[i] = this.resolveDependency(new MethodParameter(method, i), beanName);}return arguments;
}

详细解读:

  • 参数解析:该方法首先获取方法的参数类型 paramTypes,然后逐个参数解析它们的依赖。每个参数的依赖解析通过 resolveDependency 方法完成,这个方法会从 Spring 的 IoC 容器中找到相应类型的 Bean 并返回。

  • 返回参数数组:所有解析出的依赖被存放在 arguments 数组中,最终该数组将作为参数传递给目标对象的 Setter 方法。

总结

通过这篇文章的自定义实现和Spring 5.x源码解读,你应该对Spring中的依赖注入机制有了深入的理解。这些知识将帮助你更好地使用Spring框架进行企业级应用的开发。


互动与思考

在实际项目中,你是否遇到过依赖管理的复杂问题?通过实现自定义IoC容器,你对Spring的依赖注入机制有了哪些新的认识?欢迎在评论区分享你的思考和问题!


如果你觉得这篇文章对你有帮助,请别忘了:

  • 点赞
  • 收藏 📁
  • 关注 👀

让我们一起深入学习Spring框架,成为更优秀的开发者!


这篇关于Spring 源码解读:实现依赖注入的构造函数与Setter注入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1107691

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置