详细聊聊spring核心思想

news/2024/4/29 13:03:31/文章来源:https://blog.csdn.net/weixin_60227714/article/details/128953827

犹记我当年初学 Spring 时,还需写一个个 XML 文件,当时心里不知所以然,跟着网上的步骤一个一个配置下来,配错一个看着 error 懵半天,不知所谓地瞎改到最后能跑就行,暗自感叹 tmd 这玩意真复杂。

到后来用上 SpringBoot,看起来少了很多 XML 配置,心里暗暗高兴。起初根据默认配置跑的很正常,后面到需要改动的时候,我都不知道从哪下手。

稀里糊涂地在大部分时候也能用,但是遇到奇怪点的问题都得找老李帮忙解决。

到后面发现还有 SpringCloud ,微服务的时代来临了,我想不能再这般“犹抱琵琶半遮面”地使用 Spring 全家桶了。

一时间就钻入各种 SpringCloud 细节源码中,希望能领悟框架真谛,最终无功而返且黯然伤神,再次感叹 tmd 这玩意真复杂。

其间我已经意识到了是对 Spring 基础框架的不熟悉,导致很多封装点都不理解。

毕竟 SpringCloud 是基于 SpringBoot,而 SpringBoot 是基于 Spring。

于是乎我又回头重学 Spring,不再一来就是扎入各种细节中,我换了个策略,先从高纬角度总览 Spring ,理解核心原理后再攻克各种分支脉路。

于是我,我变强了。

其实学任何东西都是一样,先要总览全貌再深入其中,等回过头之后再进行总结。

这篇我打算用自己的理解来阐述下 Spring 的核心(思想),碍于个人表达能力可能有不对或啰嗦的地方,还请担待,如有错误恳请指出。

抛开IOC、DI去想为什么要有Spring

在初学 Java 时,我们理所当然得会写出这样的代码:

public class ServiceA { private ServiceB serviceB = new ServiceB();
}

我们把一些逻辑封装到 ServiceB 中,当 ServiceA 需用到这些逻辑时候,在 ServiceA 内部 new ServiceB 

如果 ServiceB 封装的逻辑非常通用,还会有 ServiceC.....ServiceF等都需要依赖它,也就是说代码里面各个地方都需要 new 个ServiceB ,这样一来如果它的构造方法发生变化,你就要在所有用到它的地方进行代码修改。

比如 ServiceB 实例的创建需要 ServiceC ,代码就改成这样:

public class ServiceA { private ServiceB serviceB = new ServiceB(new ServiceC());
}

确实有这个问题。

但实际上如若我们封装通用的service 逻辑,没必要每次都 new 个实例,也就是说单例就够了,我们的系统只需要 new一个 ServiceB 供各个对象使用,就能解决这个问题。

public class ServiceA { private ServiceB serviceB = ServiceB.getInstance();
}public class ServiceB {private static ServiceB instance = new ServiceB(new ServiceC());private ServiceB(){}public static ServiceB getInstance(){return instance;}
}

看起来好像解决问题了,其实不然。

当项目比较小时,例如大学的大作业,上面这个操作其实问题不大,但是一到企业级应用上来说就复杂了。

因为涉及的逻辑多,封装的服务类也多,之间的依赖也复杂,代码中可能要有ServiceB1ServiceB2...ServiceB100,而且相互之间还可能有依赖关系。

抛开依赖不说,就拿 ServiceB单纯的单例逻辑代码,重复的逻辑可能需要写成百上千份。

且扩展不易,以前可能 ServiceB 的操作都不需要事务,后面要上事务了,因此需要改 ServiceB 的代码,嵌入事务相关逻辑。

没过多久 ServiceC 也要事务,一模一样关于事务的代码又得在 ServiceC 上重复一遍,还有D、E、F...

对几个 Service 事务要求又不一样,还有嵌套事务的问题,总之有点麻烦。

忙了一段时间满足事务需求,上线了,想着终于脱离了重复代码的噩梦可以好好休息一波。

紧接着又来了个需求,因为经常要排查线上问题,所以接口入参要打个日志,方便问题排查,又得大刀阔斧操作一波全部改一遍。

有需求要改动很正常,但是每次改动需要做一大堆重复性工作,又累又没技术含量还容易漏,这就不够优雅了。

所以有人就开始想办法,想从这个耦合泥沼中脱离出来。

拔高和剥离

人类绝大部分的发明都是因为懒,人们讨厌重复的工作,而计算机最喜欢也最适合做重复的工作。

既然之前的开发会有很多重复的工作,那为什么不制造一个“东西”出来帮我们做这类重复的事情呢?

就像以前人们手工一步一步组装制造产品,每天一样的步骤可能要重复上万次,到后面人们研究出全自动机器来帮我们制造产品,解放了人们的双手还提高了生产效率。

拔高了这个思想后,编码的逻辑就从我们程序员想着且写着 ServiceA 依赖具体的 ServiceB ,且一个字母一个字母的敲完 ServiceB 具体是如何实例化的代码,变成我们只关心 ServiceA 依赖 ServiceB,但 ServiceB 是如何生成的我们不管,由那个“东西”帮我们生成它且关联好 ServiceA 和 ServiceB。

public class ServiceA { @注入private ServiceB serviceB;
}

听起来好像有点悬乎,其实不然。

还是拿机器说事,我们创造这台机器,如果要生产产品 A,我们只要画好图纸 A,将图纸 A 塞到这个机器里,机器识别图纸 A,按照我们图纸 A 的设计制造出我们要的产品 A。

Spring就是这台机器,图纸就是依托 Spring 管理的对象代码以及那些 XML 文件(或标注了@Configuration的类)。

这时候逻辑就转变了。程序员知道 ServiceA 具体依赖哪个 ServiceB,但是我们不需要显示的在代码中写上完整的关于如何创建 ServiceB 的逻辑,我们只需要写好配置文件,具体地创建和关联由 Spring 帮我们做。

继续拿机器举例,我们给了图纸(配置),机器帮我们制造产品,具体如何制造出来不需要我们操心,但是我们心里是有数的,因为我们的图纸写明了制造 ServiceA  需要哪样的 ServiceB,而那样的 ServiceB 又需要哪样的 ServiceC等等逻辑。

我找个图纸例子,Spring 里关于数据库的配置:

可以看到我们的图纸写的很清楚,创建 mybatis 的MapperScannerConfigurer需要告诉它两个属性的值,比如第一个是sqlSessionFactoryBeanName,值是 sqlSessionFactory

sqlSessionFactory又依赖 dataSource,而  dataSource 又需要配置好 driverClassNameurl 等等。

所以,其实我们心里很清楚一个产品(Bean)要创建的话具体需要什么东西,只过不这个创建过程由 Spring 代劳了,我们只需要清楚的告诉它即可。

因此,不是说用了 Spring 我们不再关心 ServiceA 具体依赖怎样的 ServiceBServiceB具体是如何创建成功的,而是说这些对象组装的过程由 Spring 帮我们做好。

我们还是需要清楚地知道对象是如何创建的,因为我们需要画好正确的图纸告诉 Spring。

所以 Spring 其实就是一台机器,根据我们给它的图纸,自动帮我们创建关联对象供我们使用,我们不需要显示得在代码中写好完整的创建代码。

这些由 Spring 创建的对象实例,叫作 Bean。

我们如果要使用这些 Bean 可以从 Spring 中拿,Spring 将这些创建好的单例 Bean 放在一个 Map 中,通过名字或者类型我们可以获取这些 Bean。

这就是 IOC。

也正因为这些 Bean 都需要经过 Spring 这台机器创建,不再是懒散地在代码的各个角落创建,我们就能很方便的基于这个统一收口做很多事情。

比如当我们的 ServiceB 标注了 @Transactional 注解,由 Spring 解析到这个注解就能明白这个 ServiceB 是需要事务的,于是乎织入的事务的开启、提交、回滚等操作。

但凡标记了 @Transactional 注解的都自动添加事务逻辑,这对我们而言减轻了太多重复的代码,只要在需要事务的方法或类上添加 @Transactional注解即可由 Spring 帮我们补充上事务功能,重复的操作都由 Spring 完成。

再比如我们需要在所有的 controller 上记录请求入参,这也非常简单,我们只要写个配置,告诉 Spring xxx路径(controller包路径)下的类的每个方法的入参都需要记录在 log 里,并且把日志打印逻辑代码也写上。

Spring 解析完这个配置后就得到了这个命令,于是乎在创建后面的 Bean 时就看看它所处的包是否符合上述的配置,若符合就把我们添加日志打印逻辑和原有的逻辑编织起来。

这样就把重复的日志打印动作操作抽象成一个配置,Spring 这台机器识别配置后执行我们的命令完成这些重复的动作。

这就叫 AOP。

至此我相信你对 Spring 的由来和核心概念有了一定的了解,基于上面的特性能做的东西有很多。

因为有了 Spring 这个机器统一收口处理,我们就可以灵活在不同时期提供很多扩展点,比如配置文件解析的时候、Bean初始化的前后,Bean实例化的前后等等。

基于这些扩展点就能实现很多功能,例如 Bean 的选择性加载、占位符的替换、代理类(事务等)的生成。

好比 SpringBoot Redis 客户端的选择,默认会导入 lettuce 和 jedis两个客户端配置

 

基于配置的先后顺序会优先导入 lettuce,然后再导入 jedis。

如果扫描发现有 lettuce 那么就用 lettuce 的 RedisConnectionFactory,而后面再加载 jedis 时,会基于@ConditionalOnMissingBean(RedisConnectionFactory.class) 来保证 jedis不会被注入,反之就会被注入。

ps:@ConditionalOnMissingBean(xx.class) 如果当前没有xx.class才能生成被这个注解修饰的bean

就上面这个特性就是基于 Spring 提供的扩展点来实现的。

很灵活地让我们替换所需的 redis 客户端,不用改任何使用的代码,只需要改个依赖,比如要从默认的 lettuce 变成 jedis,只需要改个 maven 配置,去除 lettuce 依赖,引入 jedis:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>

说这么多其实就是想表达:Spring 全家桶提供的这些扩展和封装可以灵活地满足我们的诸多需求,而这些灵活都是基于 Spring 的核心 IOC 和 AOP 而来的。

最后

最后我用一段话来简单描述下 Spring 的原理:

Spring 根据我们提供的配置类和XML配置文件,解析其中的内容,得到它需要管理的 Bean 的信息以及之间的关联,并且 Spring 暴露出很多扩展点供我们定制,如 BeanFactoryPostProcessorBeanPostProcessor,我们只需要实现这个接口就可以进行一些定制化的操作。

Spring 得到 Bean 的信息后会根据反射来创建 Bean 实例,组装 Bean 之间的依赖关系,其中就会穿插进原生的或我们定义的相关PostProcessor来改造Bean,替换一些属性或代理原先的 Bean 逻辑。

最终创建完所有配置要求的Bean,将单例的 Bean 存储在 map 中,提供 BeanFactory 供我们获取使用 Bean。

使得我们编码过程无需再关注 Bean 具体是如何创建的,也节省了很多重复性地编码动作,这些都由我们创建的机器——Spring帮我们代劳。

大概就说这么多了,我自己读了几遍也不知道到底有没有把我想表达的东西说明白,其实我本来从源码层面来聊这个核心的,但是怕更难说清。

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

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

相关文章

C语言入门教程||C语言 循环||C语言 函数

C语言 循环有的时候&#xff0c;可能需要多次执行同一块代码。一般情况下&#xff0c;语句是顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执…

【Django】云笔记项目

一、介绍 用户可在系统中记录自己的笔记&#xff0c;用户的数据被存储在云笔记平台&#xff1b;用户和用户之间的数据为隔离存储&#xff08;登陆后才能使用相关笔记功能&#xff0c;且只能查阅自己的笔记&#xff09; 二、功能拆解 1、用户模块 注册&#xff1a;成为平台…

【Java 面试合集】简述下自定义异常的应用场景

简述下自定义异常的应用场景 1. 概述 如上图所示&#xff0c;我们想回答这个问题就要了解异常的基本结构。哪些是我们可以控制的&#xff0c;哪些是我们不能控制的。 也许有人会问了&#xff0c;其实在逻辑中可以多加判断&#xff0c;为什么要需要自定义呢。 其实判断的内容无…

跳跃游戏 II 解析

题目描述给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处:0 < j < nums[i] i j < n返回到达 nums[n - 1] 的…

【i2c协议介绍】

文章目录协议简单介绍五种速度模式master/slave和transmitter/receiver关系第一种情况&#xff1a;master作为transmitter&#xff0c;slave作为receiver第二种情况&#xff1a;当master作为receiver&#xff0c;slave作为transmitteri2c基本信号start产生stop信号数据传输有效…

基于OpenCV 的车牌识别

基于OpenCV 的车牌识别 车牌识别是一种图像处理技术&#xff0c;用于识别不同车辆。这项技术被广泛用于各种安全检测中。现在让我一起基于 OpenCV 编写 Python 代码来完成这一任务。 车牌识别的相关步骤 1. 车牌检测&#xff1a;第一步是从汽车上检测车牌所在位置。我们将使用…

《Spring揭秘》记录

IOC部分 IOC不等于IOC容器&#xff0c;即使不使用spring&#xff0c;我们也可以使用IOC&#xff0c;只不过spring提供了IOC容器实现。Spring的IoC容器的功能就包含一个提供依赖注入服务的IoC Service Provider。它提供两方面的支持&#xff0c;业务对象的构建管理和业务对象间的…

python读取.stl文件

目录 .1 文本方式读取 1.2 stl解析 1.3 stl创建 .2 把点转换为.stl .1 文本方式读取 代码如下 stl_path/home/pxing/codes/point_improve/data/003_cracker_box/0.stlpoints[] f open(stl_path) lines f.readlines() prefixvertex num3 for line in lines:#print (l…

《里奥哈酒友记》 | 里奥哈的历史—品鉴瑞格尔侯爵葡萄酒

2022年末&#xff0c;里奥哈大使组合怪怪和思羽完成了里奥哈线上活动两大“壮举”&#xff0c;10期《里奥哈酒友记》系列视频和40集《美酒之乡——里奥哈》有声专辑&#xff0c;吸引了许多葡萄酒资深爱好者的目光&#xff0c;也成功地让更多的人了解到里奥哈。由里奥哈推广大使…

windows无法访问指定设备路径或文件怎么办?2个解决方案

有时候Win10电脑打不开程序或文件&#xff0c;windows无法访问指定设备路径或文件该怎么办&#xff1f;原因是什么呢&#xff1f;一般导致这种情况的出现&#xff0c;大多是因为我们的电脑缺乏相应的查看权限&#xff0c;我们只需要通过赋予权限就可以解决这个难题了。 操作环境…

【Java】TCP的三次握手和四次挥手

三次握手 TCP三次握手是一个经典的面试题&#xff0c;它指的是TCP在传递数据之前需要进行三次交互才能正式建立连接&#xff0c;并进行数据传递。&#xff08;客户端主动发起的&#xff09;TCP之所以需要三次握手是因为TCP双方都是全双工的。 什么是全双工&#xff1f; TCP任何…

手把手教你将Eureka升级Nacos注册中心

由于原有SpringCloud体系版本比较老&#xff0c;最初的注册中心使用的Eureka后期官方无升级方案&#xff0c;配置中心无法在线管理配置&#xff0c;还有实时上下线的问题&#xff0c;因此需要将原有系统的Eureka服务升级Nacos注册心服务。原有版本SpringBoot1.5.15、SpringClou…

C#【必备技能篇】Winform跨线程更新进度条的实例

文章目录实例一&#xff1a;【方便理解&#xff0c;常用&#xff01;】源码&#xff1a;运行效果&#xff1a;实例二&#xff1a;【重在理解代码本身】源码&#xff1a;运行效果&#xff1a;参考&#xff1a;实例一&#xff1a;【方便理解&#xff0c;常用&#xff01;】 跨线…

独立图片服务器有什么突出之处

服务器是网络中非常重要的设施&#xff0c;承载着不同流量的访问&#xff0c;这就要求服务器具有快速的吞吐量、高稳定性和高可靠性。独立图片服务器作为独立服务器的衍生品&#xff0c;在数据利用方面的应用可以为企业在数据处理和分析方面带来一场革命。本文就将介绍独立图片…

Cadence OrCAD 16.6 原理图导出带标签pdf(免费软件版)

Cadence OrCAD 16.6原理图导出带标签pdf&#xff08;免费软件版&#xff09; Cadence OrCAD 16.6 导出有标签的原理图&#xff0c;页面导航、跨页符、元件封装等&#xff0c;更方便阅读。找了一些可用的免费软件。 安装软件 系统win10 22H2&#xff0c;OrCAD SPB 16.6 根据…

你评论,我赠书~【哈士奇赠书 - 13期】-〖Python程序设计-编程基础、Web开发及数据分析〗参与评论,即可有机获得

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

【手写 Vuex 源码】第一篇 - Vuex 的基本使用

一&#xff0c;前言 本篇开始&#xff0c;进入 vuex 源码学习&#xff0c;本篇主要介绍一下内容&#xff1a; 创建 vuex 源码项目&#xff1b;介绍 vuex 的基本使用&#xff1b; 二&#xff0c;创建 vuex 源码项目 1&#xff0c;使用 vue-cli 创建 vue2.x 脚手架 vue creat…

OpenSergo Spring Cloud Alibaba 带来的服务治理能力

作者&#xff1a;十眠、牧思 Spring Cloud 应用为何需要服务治理 随着微服务技术的发展&#xff0c;微服务(MicroServices) 的概念早已深入人心&#xff0c;越来越多的公司开始使用微服务架构来开发业务应用。 如果采用得当&#xff0c;微服务架构可以带来非常大的优势。微服…

linux 学习(持续更新)

一&#xff1a;初识linux 新装操作环境&#xff1a; mac intel电脑 CentOS系统版本&#xff1a;CentOS-8.1.1911 在这里解释一下[chenllocalhost /]$这句话的含义&#xff1a; chenl是用户名&#xff0c;也就是你自己起的名字。 是分割的符号 localhost是主机名&#xff0c;也…

“搜索大战”正式打响,微软发布ChatGPT版搜索引擎和浏览器

微软公司宣布推出由ChatGPT支持的最新版本Bing&#xff08;必应&#xff09;搜索引擎和Edge浏览器&#xff0c;今天上线&#xff0c;免费使用&#xff01; 自去年开始&#xff0c;Stable Diffusion、ChatGPT 等 AI 工具的横空出世&#xff0c;貌似在告诉人们“AI 正在准备重塑整…