Spring IOC深入理解之源码实现

1、Spring IOC的理解

IOC:Inversion Of Control,即控制反转,是一种设计思想。在传统的 Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象;而在Spring程序设计中,IOC 是有专门的容器去控制对象。

控制反转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码 中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服 务,容器将他们组件起来。在一般的IOC场景中容器创建了所有的对象,并设置了必 要的属性将他们联系在一起,等到需要使用的时候才把他们声明出来,使用注解就跟 方便了,容器会自动根据注解把对象组合起来

所谓控制就是对象的创建、初始化、销毁。

  • 创建对象:原来是 new 一个,现在是由 Spring 容器创建。

  • 初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。

  • 销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。

所谓反转:其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了 Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。

总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。

IOC解决了什么问题

  1. 对象之间的耦合度或者说依赖程度降低;

  2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

IOC和DI的区别

IoC(Inverse of Control:控制反转)是一种设计思想或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权交给第三方比如 IoC 容器。 对于我们常用的 Spring 框架来说, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。不过,IoC 在其他语言中也有应用,并非 Spring 特有。

IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。

IOC思想是一种目标,DI是实现这种思想的目标的手段.

控制反转(IOC)有什么用?

  • 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要我们来维护的话,那是相当头疼的

  • 解耦,由Spring容器去维护具体的对象

  • 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的

IOC的优点有哪些?

  • IOC(依赖注入)把应用的代码量降到最低

  • 它使应用容易测试,单元测试不在需要单例和JNDI查找机制

  • 最小的代价和最小的侵入性使松散耦合得以实现

  • IOC容器支持加载服务时的饿汉式初始化和懒加载

Spring IOC的实现机制

工厂模式+反射机制

  1. 工厂模式

    • Spring使用了三种工厂模式(静态工厂、实例工厂、自定义工厂)来创建和管理Bean对象。

    • 在工厂模式中,对象的创建逻辑对客户端是隐藏的,客户端只需知道接口而无需关心具体的实现细节。

    • 通过工厂模式,Spring可以灵活地创建和管理Bean对象,支持扩展和配置。

  2. Java反射机制

    • Java反射是Spring实现IOC的关键技术之一。它允许程序在运行时动态地获取类的内部信息(如属性、方法、构造器等),并可以动态地创建对象、调用方法、修改属性等。

    • 在Spring中,反射机制被用于加载类、解析配置文件、创建和管理Bean对象等。

    • 通过反射,Spring可以读取配置文件中的Bean定义,并根据定义动态地创建Bean对象,同时处理Bean之间的依赖关系。

Spring IOC的实现机制可以归纳如下:

  • 定义Bean:通过XML配置文件、Java注解或Java配置类等方式定义应用程序中的各个组件(Bean)。

  • 配置依赖关系:在定义Bean的同时,通过配置文件或注解等方式指定Bean之间的依赖关系。

  • 创建容器:创建一个Spring容器实例(如ApplicationContext),该容器负责管理和创建Bean。

  • 容器初始化:容器读取配置信息并初始化相应的Bean。这涉及到实例化对象、设置属性值、解析依赖关系等操作。

  • 依赖注入:在容器初始化的过程中,Spring容器会自动解析Bean之间的依赖关系,并通过反射机制将依赖的对象注入到需要的地方。这可以通过构造函数、Setter方法或字段注入等方式进行。

  • 使用Bean:容器初始化完成后,可以从容器中获取需要的Bean,并使用它们来执行相应的业务逻辑。

Spring提供了两种主要的IoC容器实现

  1. BeanFactory: 是Spring IoC容器的基本实现,它是一个工厂模式的实现,延迟加载对象。

  2. ApplicationContext: 是BeanFactory的扩展,提供了更多的功能,如国际化处理、事件传递、应用程序层特定上下文的配置等。

Spring IoC的工作流程

  1. 配置: 通过XML配置文件、注解或Java配置类配置Bean的信息和依赖关系。

  2. 实例化: IoC容器根据配置文件中定义的信息实例化Bean。

  3. 注入: IoC容器将Bean之间的依赖关系通过构造函数注入或Setter注入的方式进行赋值。

  4. 管理: IoC容器管理Bean的生命周期,包括初始化、使用和销毁。

2、代码实现

1)代码测试

public class SpringIOCTest {@Testpublic void testScan() throws FileNotFoundException {SpringIOC springIOC = new SpringIOC();springIOC.initBeans();TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());instance.test();}
}

测试结果:

zhangsan
18 

2)定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}

定义两个注解@Autowired和@Component,SpringIoc中注解用来配置和管理Spring应用中的组件和依赖关系

@Target(ElementType.FIELD):这个元注解(meta-annotation)指定了@Autowired注解可以应用的Java元素类型。在这个例子中,它指定了@Autowired和@Component只能应用于字段(FIELD)。但实际上,Spring的@Autowired注解还可以应用于方法(METHOD)和构造器(CONSTRUCTOR)上。这里的描述是为了简化而只提到了字段。
@Retention(RetentionPolicy.RUNTIME):这个元注解指定了@Autowired和@Component注解的保留策略。RetentionPolicy.RUNTIME意味着这个注解不仅被保留在.class文件中,而且在运行时通过反射也是可访问的。这对于Spring框架在运行时通过反射机制读取注解并据此执行自动装配操作是必需的。

3)SpringIOC核心类

接下来由测试代码一步步进行分析SpringIOC的实现

第一步:IOC容器实例化,获取类信息

SpringIOC springIOC = new SpringIOC();
public SpringIOC() {initPath();try {scan();} catch (FileNotFoundException e) {e.printStackTrace();}beanNames= new ArrayList<>();initBeanNames();}

调用SpingIOC进行类实例化时,首先该类声明了五个全局变量如下,

private List<String> beanNames;   //用来存储所有包名
private List<String> filePaths;   //用来存储所有文件全路径
private String basePath;    //用来存储类的路径
private String basePackage;    //用来存储类的包路径
private Map<String, Object> beans =new HashMap<>();    //用来存储带有注解的类名->该类里面注入的对象

① initPath():获取当前类的类路径和包路径

private void initPath(){basePath="D:\\daima\\ioc-master\\src\\main\\java\\com\\xxhh\\springioc\\";basePackage="com.xxhh.springioc";}

② scan():扫描所有的文件信息,存到 filePaths

private void scan() throws FileNotFoundException {File file = new File(basePath);filePaths = new ArrayList<>();if(file.exists()){Queue<File> queue = new LinkedList<>();queue.add(file);while(!queue.isEmpty()){File poll = queue.poll();if(poll == null){continue;}if(poll.isDirectory()){File[] files = poll.listFiles();for (File f : files) {queue.add(f);}}else {filePaths.add(poll.getPath());}}}else {throw new FileNotFoundException(basePath+" not found");}}

③ initBeanNames():将所有的.java结尾的文件的全限定名放到 beanNames

public void  initBeanNames(){for (String s : filePaths) {String replace = s.replace(basePath, "");if(replace.endsWith(".java")) {replace = replace.substring(0, replace.length()-5);}char[] chars = replace.toCharArray();for (int i = 0; i < chars.length; i++) {if(chars[i]=='\\'){chars[i] = '.';}}beanNames.add(basePackage+"."+new String(chars));}}

第二步:Bean的实例化与注册以及依赖自动注入

springIOC.initBeans();
public void initBeans(){for (String beanName : beanNames) {try {Class<?> aClass = Class.forName(beanName);//获取类注解Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();for (Annotation declaredAnnotation : declaredAnnotations) {if(declaredAnnotation instanceof Component){Object o = aClass.newInstance();beans.put(aClass.getName(),o);}}} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();}}for (Map.Entry<String, Object> entry : beans.entrySet()) {//获取类中的字段Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();for (Field field : declaredFields) {Annotation[] declaredAnnotations = field.getDeclaredAnnotations();for (Annotation annotation : declaredAnnotations) {if(annotation instanceof Autowired ){//field 需要由我们来赋值// 我们所持有的所有对象 在beans中// 根据当前域中的类型的名字String name = field.getType().getName();// 从beans 中获得对应的对象Object o = beans.get(name);//设置字段的访问权限,使其即使是私有的(private)或受保护的(protected),也可以通过反射被访问和修改field.setAccessible(true);try {field.set(entry.getValue(), o);} catch (IllegalAccessException e) {e.printStackTrace();}}}}}}

步骤 1: Bean的实例化与注册

  1. 遍历所有Bean名称for (String beanName : beanNames) 循环遍历在之前步骤(在initBeanNames方法)中收集的所有Bean的全限定名(即包括包名的类名)。

  2. 加载类并检查注解:对于每个Bean名称,使用Class.forName(beanName)加载类。然后,通过getDeclaredAnnotations()获取该类上声明的所有注解。

  3. 实例化并注册Bean:在类的注解数组中,检查是否存在@Component(或任何其他表示该类应被注册为Bean的注解,这里假设@Component是检查的重点)。如果找到,则通过aClass.newInstance()(注意:在Java 9及更高版本中,推荐使用Class.getDeclaredConstructor().newInstance()的替代方案,如Class.getDeclaredConstructor().newInstance()的等价物,因为newInstance()方法已被标记为过时)实例化该类,并将其添加到beans映射中,键为类的全限定名,值为类的实例。

步骤 2: 依赖的自动注入

  1. 遍历已注册的Beanfor (Map.Entry<String, Object> entry : beans.entrySet()) 循环遍历beans映射中的所有条目,每个条目代表一个已注册的Bean及其实例。

  2. 获取并遍历Bean的字段:对于每个Bean实例,使用entry.getValue().getClass().getDeclaredFields()获取其所有字段。然后,遍历这些字段。

  3. 检查字段的注解:对于每个字段,通过getDeclaredAnnotations()获取其声明的所有注解。

  4. 自动注入依赖:在字段的注解数组中,检查是否存在@Autowired注解。如果存在,表明该字段需要自动注入依赖。

    a. 获取依赖的Bean名称:通过field.getType().getName()获取依赖的类型的全限定名,这里假设该名称直接用作Bean的键(在真实应用中,这可能需要更复杂的逻辑,如支持类型到Bean名称的映射)。

    b. beans映射中获取依赖的Bean实例:使用依赖的类型的全限定名作为键,从beans映射中获取对应的Bean实例。

    c. 设置字段值:通过反射将依赖的Bean实例设置到当前Bean实例的字段中。首先,通过field.setAccessible(true)确保即使字段是私有的或受保护的,也可以被访问。然后,使用field.set(entry.getValue(), o)将依赖的Bean实例注入到当前Bean实例的字段中。

第三步:获取TestController类的实例

TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());
 public Object getInstance(String beanName) {return beans.get(beanName);}

根据TestController类名找到该类里面注入的对象,即找到userService

TestController实现如下:

import com.xxhh.springioc.stereotype.Autowired;
import com.xxhh.springioc.stereotype.Component;@Component
public class TestController {@Autowiredprivate UserService userService;public void test(){userService.addUser("zhangsan",18);}}

UserService实现如下:

import com.xxhh.springioc.stereotype.Component;@Component
public class UserService {public void addUser(String name, int age) {System.out.println(name);System.out.println(age);}
}

第四步:调用TestController实例的方法

instance.test();

由于TestController中的test()方法中调用userService中的addUser(String name, int age)方法,最终输出zhangsan 18

以上就是简单实现SpringIOC的全部源码,如有错误,欢迎指正!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/45498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文研读:ViT-V-Net—用于无监督3D医学图像配准的Vision Transformer

目录 摘要 介绍 方法 VIT-V-Net体系结构 损失函数 图像相似性度量 变形场正则化 结果与讨论 摘要 在过去的十年里&#xff0c;卷积神经网络(ConvNets)在各种医学成像应用中占据了主导地位并取得了最先进的性能。然而&#xff0c;由于缺乏对图像中远程空间关系的理解&a…

Gitlab CI/CD介绍

基本概念 GitLab CI/CD&#xff08;持续集成/持续部署&#xff09;流水线是GitLab平台提供的一项强大功能&#xff0c;旨在通过自动化构建、测试和部署过程&#xff0c;提高开发团队的效率和软件发布的质量。 CI&#xff08;Continuous Integration&#xff09;&#xff1a;持续…

QT5.12.9 通过MinGW64 / MinGW32 cmake编译Opencv4.5.1

一、安装前准备: 1.安装QT,QT5.12.9官方下载链接:https://download.qt.io/archive/qt/5.12/5.12.9/ QT安装教程:https://blog.csdn.net/Mark_md/article/details/108614209 如果电脑是64位就编译器选择MinGW64,32位就选择MinGW32,我的是MinGW64。 2.opencv源码下载:h…

linux登入提示信息

目录 1.Linux 登录提示信息在操作系统中扮演着重要的角色 安全性提醒 欢迎信息 系统状态通知 政策和使用条款 技术支持信息 更新和变更通知 2.配置文件介绍 3.编辑配置文件 4.效果展示 修改前 修改后 “如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经…

ActiViz实战:基于ActiViz 9.2的医学影像三维重建MPR的简单示例

文章目录 效果预览核心代码源码地址总结效果预览 基于ActiViz 9.2的医学影像三维重建之MPR重建 核心代码 vtkProperty ipwProp = vtkProperty.New(); vtkImagePlaneWidget[] planeWidget = new vtkImagePlaneWidget

面试题 14- I. 剪绳子

剪绳子 题目描述示例 题解 题目描述 给你一根长度为 n 的绳子&#xff0c;请把绳子剪成整数长度的 m 段&#xff08;m、n都是整数&#xff0c;n>1并且m>1&#xff09;&#xff0c;每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0] * k[1] *...* k[m-1] 可能的最大乘积…

14-50 剑和诗人24 - 开源 AI 的下一个重大飞跃:多个小模型与大模型相媲美

介绍 大型语言模型 (LLM) 领域最近取得了快速进展&#xff0c;GPT-4、PaLM-2、Llama-2 等模型正在突破 AI 对语言处理能力的界限。然而&#xff0c;只有少数几家大型科技公司拥有训练包含数千亿个参数的模型所需的大量计算资源&#xff0c;才能使用最大的模型。 作为回应&…

鼠标怎么挑选

我们平时工作中经常要用到鼠标&#xff0c;那么哪种鼠标比较好。我们今天就来研究下。 手感要好 鼠标最重要的就是手感。这个看参数看不出来。最好能到实体店里面去体验一下&#xff0c;自己上手试一下。 如果现在都是在网上买了&#xff0c;去实体店过于麻烦&#xff0c;并且…

[题解]P1115 最大子段和

本蒟蒻太菜了&#xff0c;还不太能理解为什么&#xff0c;先存一下题解&#xff0c;有时间再来研究。 分治法 O ( n log ⁡ n ) O(n\log n) O(nlogn) #include<bits/stdc.h>using namespace std; using lllong long; int n; vector<int>v; int Find(int l,int r)…

[Yii] 实战开发微信服务号+Yii 2.0构建商城系统全栈应用

YII 实战开发微信服务号Yii 2.0构建商城系统全栈应用 课程地址&#xff1a;http://ityuanke.com/thread-14-1-1.html 微信服务号Yii2.0构建商城系统全栈应用 依托微信服务号高级特性及Yii 2.0技术为核心&#xff0c;手把手带你从零开发一个带完整PC后台的微信图书商城的全栈应用…

centos 安装vnc,配置图形界面

centos 安装vnc,配置图形界面 在CentOS上安装和配置VNC服务以使用图形用户界面&#xff0c;你可以按照以下步骤操作&#xff1a; 安装VNC服务器 sudo yum install -y tigervnc-server 复制VNC配置模板文件 cp /lib/systemd/system/vncserver.service /etc/systemd/system/…

各个系统配置端口转发

iptables配置脚本 记得提前安装iptables # Centos/Redhat ~# yum install iptables-services # Ubuntu/Debian ~# apt-get install iptables-persistent#!/bin/bash# 启用 IP 转发 echo 1 > /proc/sys/net/ipv4/ip_forward# 配置 iptables 规则 iptables -t nat -A PREROU…

Git 详解(原理、使用)

git 快速上手请看这篇博客 Git 快速上手 1. 什么是 Git Git 是目前最主流的一个版本控制器&#xff0c;并且是分布式版本控制系统&#xff0c;可以控制电脑上所有格式的文档 版本控制器&#xff1a;记录每次修改以及版本迭代的管理系统 对于文本文件&#xff0c;可以记录每次…

安全防御-用户认证综合实验

一、拓扑图 二、实验要求 1、DMZ区的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9:00-18:00&#xff09;可以访问&#xff0c;生产区设备全天都是可以访问的 2、生产区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网 3、办公区设备10.0.2.20不允许访…

iMazing 3.0.3.1Mac中文破解版下载安装激活

今天&#xff0c;小编要分享的是Mac下一款可以帮助用户管理IOS设备的软件——iMazing&#xff0c;之前&#xff0c;小编也分享的过类似的软件&#xff0c;iMazing却有独特之处。小子这次带来的是3.0.3.1版本。 iMazing 3是一款iOS设备管理软件&#xff0c;该软件支持对基于iOS…

从代理模式到注解开发(一)

代理模式 package org.example.proxy;public class ProxyClient {public static void main(String[] args) {ProxyBuilder proxyBuilder new ProxyBuilder();proxyBuilder.build();} }interface BuildDream {void build(); }class CustomBuilder implements BuildDream {Over…

MetaGPT和LangGraph对比

MetaGPT和LangGraph是两个不同的AI Agent框架&#xff0c;各有其特点和优势:MetaGPT: MetaGPT是一个多Agent协作框架&#xff0c;模拟软件公司的运作方式。它包含多个角色如产品经理、架构师、项目经理和工程师&#xff0c;每个角色都有特定的职责。MetaGPT采用对话模式&#…

你也想做一个Element-ui吧!!!——Blueの前端路(一)

目录 前言&#xff1a; 父子组件 button组件 使用vue脚手架初始化一个项目 如何封装&#xff0c;注册和使用一个组件 main.js中将组件设置为全局 使用 此组件我们所需实现的内容 type 父组件组件传递type属性 子组件接收负组件传递的数据 通过绑定类名的方法动态控制…

MP4怎么转为MP3?超多人都在用的四种转换方法!

MP4怎么转为MP3&#xff1f;MP4&#xff0c;这一风靡全球的多媒体容器格式&#xff0c;无疑是数字时代的一枚璀璨明珠&#xff0c;深深烙印在每个人的数字生活之中&#xff0c;那么&#xff0c;它究竟是如何在众多格式中脱颖而出&#xff0c;赢得如此广泛认可的呢&#xff1f;首…

【qt】TCP服务端发消息给客户端

在使用Qt的网络编程中&#xff0c;数据的传输通常使用QByteArray来进行. 可以用toUtf8() 来进行转换. 用write() 来写入数据 运行结果: