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中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque