您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

详细聊聊Spring核心思想

时间:2023-02-09 15:26:37  来源:今日头条  作者:Java架构日记

犹记我当年初学 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;
    }
}

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

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

因为涉及的逻辑多,封装的服务类也多,之间的依赖也复杂,代码中可能要有ServiceB1、ServiceB2...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 又需要配置好 driverClassName、url 等等。

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

因此,不是说用了 Spring 我们不再关心 ServiceA 具体依赖怎样的 ServiceB、ServiceB具体是如何创建成功的,而是说这些对象组装的过程由 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 暴露出很多扩展点供我们定制,如 BeanFactoryPostProcessor、BeanPostProcessor,我们只需要实现这个接口就可以进行一些定制化的操作。

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

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

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

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



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(59)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(41)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(21)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(91)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(88)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(98)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(93)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(137)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(128)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(126)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(16)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(12)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(15)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(8)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(38)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(21)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(120)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(128)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(126)  评论:(0)  加入收藏
站内最新
站内热门
站内头条