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

相关文章

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

java之Objects.nonNull用法代码解读

《java之Objects.nonNull用法代码解读》:本文主要介绍java之Objects.nonNull用法代码,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录Java之Objects.nonwww.chinasem.cnNull用法代码Objects.nonN

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

springboot security之前后端分离配置方式

《springbootsecurity之前后端分离配置方式》:本文主要介绍springbootsecurity之前后端分离配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的... 目录前言自定义配置认证失败自定义处理登录相关接口匿名访问前置文章总结前言spring boot secu

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI