详细聊聊spring核心思想

news2024/11/24 9:09:40

犹记我当年初学 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.coloradmin.cn/o/333848.html

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

相关文章

最短路之Dijkstra(15张图解)

&#x1f33c;多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 闲来无事听听歌 Dijkstra可解决“单源最短路径”问题 四种最短路算法 Floyd算法 时间复杂度高&#xff0c;但实现容易&#xff08;5行核心代码&#xff09;&#xff0c;可解决负权边&#xff0c;适用于数据范围…

凭借这份《2022测试八股文》候选者逆袭面试官,offer拿到手软

《2023测试面试八股文》800 道软件测试面试真题&#xff0c;高清打印版打包带走&#xff0c;横扫软件测试面试高频问题&#xff0c;涵盖测试理论、Linux、MySQL、Web 测试、接口测试、App 测试、Python、Selenium、性能测试、LordRunner、计算机网络、数据结构与算法、逻辑思维…

opencv——傅里叶变换、低通与高通滤波及直方图等操作

1、傅里叶变换a、傅里叶变换原理时域分析&#xff1a;以时间为参照进行分析。频域分析&#xff1a;相当于上帝视角一样&#xff0c;看事物层次更高&#xff0c;时域的运动在频域来看就是静止的。eg&#xff1a;投球——时域分析&#xff1a;第1分钟投了3分&#xff0c;第2分钟投…

“学好英语网”首页制作

“学好英语网”首页制作一、实验名称&#xff1a;二、实验日期&#xff1a;三、实验目的&#xff1a;四、实验内容&#xff1a;五、实验步骤&#xff1a;六、实验结果&#xff1a;七、源程序&#xff1a;八、心得体会&#xff1a;一、实验名称&#xff1a; “学好英语网”首页…

Linux第三讲

目录 三、 磁盘和文件管理和使用检测和维护 3.1 磁盘目录 3.2 安装软件 3.2.1 rpm命令 3.2.2 克隆虚拟机 3.2.3 yum或压缩包方式安装jdk 3.2.4 使用虚拟机运行SpringBoot项目 3.2.5 安装mysql80&#xff08;57&#xff09; 3.2.6 运行web项目 3.2.7 安装tomcat 三、 …

情人节前夕,竞品在小红书平台如何布局营销策略?

情人节作为全球性消费型节日之一&#xff0c;其营销价值不言而喻。以女性用户群体为主导的小红书平台&#xff0c;更是成为该营销节点众多品牌争夺流量的阵地。 那么&#xff0c;情人节前夕竞品在小红书平台布局什么样的营销策略&#xff1f;创作何种内容&#xff0c;如何推广&…

手把手教你用Python做可视化数据,还能调节动画丝滑度

数据可视化动画还在用Excel做&#xff1f; 现在一个简单的Python包就能分分钟搞定&#xff01; 而且生成的动画也足够丝滑&#xff0c;效果是酱紫的&#xff1a; 这是一位专攻Python语言的程序员开发的安装包&#xff0c;名叫Pynimate。 目前可以直接通过PyPI安装使用。 使用…

线程池小结

什么是线程池 线程池其实就是一种多线程处理形式&#xff0c;处理过程中可以将任务添加到队列中&#xff0c;然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象; 为什么使用线程池 …

1001. x+y 1002. x+y+z etiger.vip 解析与答案

目录 1001题 题目描述 输入输出格式 输入格式 输出格式 输入输出样例 输入样例#1: 输出样例#1: 头文件和数组等初始定义 第一个函数——converts 第二个函数——add 第三个函数——print 主函数部分 完整代码 1002题 题目描述 输入输出格式 输入格式 输出格…

Vue3快速入门【一】

Vue3快速入门一、Vue脚手架1.1、Vite简介1.2、创建项目二、更换Vue模板支持工具三、项目相关命令解析四、生命周期钩子函数五、ref方法的几种使用方式5.1、ref方法(操作基本类型数据)5.2、ref方法(操作复杂类型数据)5.3、ref方法获取标签六、reacttive方法和toRefs方法七、setu…

开发人员 ONLYOFFICE 文档 v7.3:API 与文档生成器更新

随着版本 7.3 新功能的发布&#xff0c;我们也对编辑器、插件以及文档生成器的 API 进行了更新。在下方了解更多详情。 增强的 WOPI 支持 从现在开始&#xff0c;您可在 WOPI 集成中使用二进制格式&#xff0c;如 doc、ppt 以及 xls。我们现已实现了相应方案&#xff0c;其中包…

ClickHouse 合并树表引擎 MergeTree 索引与数据存储方式

目录 1. 一级索引 1.1 稀疏索引 1.2 索引粒度 1.3 索引数据的生成规则 1.4 索引的查询过程 2. 二级索引 2.1 granularity 与 index_granularity 2.2 跳数索引的生成规则

阿里6面,成功唬住面试官拿了27K,软件测试面试也没有传说中那么难吧....

阿里的面试挺独特&#xff0c;每轮面试都没有 HR 约时间&#xff0c;一般是晚上 8 点左右面试官来一个电话&#xff0c;问是否能面试&#xff0c;能的话开始面&#xff0c;不能就约一个其它时间。 全程 6 面&#xff0c;前五面技术面&#xff0c;电话面试&#xff0c;最后一面…

KEITHLEY吉时利2410数字源表

产品概览 Keithley 2410 高压源表专为需要紧密耦合源和测量的测试应用而设计。Keithly 2410 提供精密电压和电流源以及测量功能。它既是高度稳定的直流电源&#xff0c;又是真正的仪器级 5-1/2 数字万用表。电源特性包括低噪声、精度和回读。万用表功能包括高重复性和低噪声。…

《真象还原》读书笔记——第三章 完善MBR(3.5 硬盘)

3.5 硬盘介绍 3.5.2 硬盘工作原理 柱面-磁头-扇区 磁道的编号从0 开始&#xff0c;相同编号的此道组成的管状区域就是柱面。 盘面和磁头一一对应&#xff0c;所以用磁头号表示盘面&#xff0c;磁头编号从上到下从0开始。 扇区编号与盘面和磁道不同&#xff0c;各磁道内的扇区都…

选择万德L2接口需要遵循什么原则?

万德L2接口是一种可以让程序拥有查询股票相关数据的应用查询编程接口&#xff0c;通过这个数据接口&#xff0c;可以直接调用相应的数据&#xff0c;而不用进行额外的编程工作&#xff0c;甚至不需要理解其中的工作机制&#xff0c;是十分方便快捷的一种数据接口。 但是市场上…

操作系统基础---多线程

文章目录操作系统基础---多线程1.为何引入线程程序并发的时空开销线程的设计思路线程的状态和线程控制块TCB2.线程与进程的比较3.线程的实现⭐1.内核支持线程KST2.用户级线程3.组合方式操作系统基础—多线程 1.为何引入线程 利用传统的进程概念和设计方法已经难以设计出适合于…

方向导数与梯度下降

文章目录方向角与方向余弦方向角方向余弦方向导数定义性质梯度下降梯度下降法&#xff08;Gradient descent&#xff09;是一个一阶最优化算法&#xff0c;通常也称为最速下降法。 要使用梯度下降法找到一个函数的局部极小值&#xff0c;必须向函数上当前点对应梯度&#xff08…

学习数据库第一天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、数据库的开启和关闭二、数据库的登录 添加数据库以及表1.登录数据库2.查看数据库三 数据表前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#x…

spring cloud脚手架快速开发 微服务架构 JAVA敏捷开发框架源码

核心技术采用Spring Cloud Alibaba、SpringBoot、Mybatis、Seata、Sentinel、RabbitMQ、FastDFS/MinIO、SkyWalking等主要框架和中间件。 私信了解更多&#xff01; 功能模块&#xff1a; 1、租户管理&#xff1a;运营人员管理所有的租户创建 2、工作台&#xff1a;普通用户…