从Spring到SpringBoot,细说其区别与演化
Spring是我们作为java开发最常用的框架了,但现如今,大部分项目实际使用的却是SpringBoot。由于它们存在一些关联,导致很多开发对其功能和目的有些混乱,例如说不清一些功能到底是Spring框架的,还是SpringBoot带的?又或者不能清晰的描述两个框架的各自功能和目的,今天,我们就来详细解释这些问题
我们既然想知道两个框架的关联,不如直接开个工程,引入一个基础的Springboot启动器,然后通过依赖关系来分辨两者的联系
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
我们先看三个带标注的包,springBoot启动器是不含代码的,它的唯一作用就是引入各个指定了版本的包,其中最重要的就是spring-boot 和 spring-boot-autoconfigure 两个springboot核心包
而我们要看spring框架 和 springBoot 框架的关系,可以直接看两段红色的依赖性,图中,spring-boot-autoconfigure自动配置包不需要再引用任何东西,而作为核心支持的spring-boot包则需要引入spring框架核心包spring-context 和 spring-core
总结来看,如果我们只关注核心包,springboot框架毫无疑问是建立在spring框架之上的,在spring框架之上,加上一些类比如SpringApplication启动器等,并且配合上自动配置功能
在Spring框架创建前,Java EE平台(即Java Enterprise Edition)是企业级应用程序开发的主要框架,该框架的提出主要是因为一个企业级应用的搭建涉及的内容太多,迫切需要一个规范来进行模块分层处理,于是sun公司(现在已经被Oracle收购)希望一些基础的共性的东西由容器去管理,而个性化的东西,如业务由组件去管理,由容器+组件的方式来提供服务。
所以这两方面:容器和组件。容器由各个开发商去根据规范开发,如WebSphere、WebLogic Server等。开发者则去开发组件,然后运行在容器中。由于容器满足了统一的规范,所以组件可以运行在不同的容器上
但 它太复杂了。在构建现代企业级应用程序时,开发人员往往需要大量的样板代码和配置,比如在web.xml文件中手动配置Servlet和JSP的映射关系,手动配置数据库连接池和JNDI数据源,手动配置EJB容器的属性以及部署EJB组件等,这些配置内容繁琐,容易出错,并且需要开发人员掌握很多细节,同时这些配置还需要在不同的JavaEE容器之间进行调整。
上面我们介绍了java EE的开发与配置过于繁杂,所以Rod Johnson于2002年创建了Spring框架,我们必须明白,spring并不是推翻了javaEE所规范的模式,Spring框架的主要目标是简化Java EE平台的开发,使开发人员可以更专注于业务逻辑,而非容器配置和其他样板代码。
比如上面的Servlet和JSP的映射关系,在spring里,可以由其MVC框架(也称为Spring Web MVC)将应用程序分解为模型、视图和控制器的模式,同时控制器可以通过注解来映射哪些请求应该由哪个方法处理,从而简化了Servlet和JSP映射配置,
除了springmvc以外,spring框架其实包含了多个包,其中 spring-core(包含spring-context、spring-beans、spring-aop)毫无疑问是重中之重。,这些核心包中包含了大量的底层设计,来帮助开发者减少代码负担
其中有两个功能和模式是最大的提升,能大量简化开发代码。这就是我们常说的Spring框架的两大核心 —— IOC 与 AOP
很多开发提及spring,都能知道Spring框架的两大核心 —— IOC 与 AOP,这也一直是面试高频题。但这两者对开发者有什么裨益却语焉不详了,我们就用最简单的话来解释一下
介绍:控制反转(Inversion of Control)的名字源于它对传统编程方法的反转。在传统编程方法中,程序员通过代码来控制各种对象之间的依赖关系,程序员需要显式地在代码中创建和管理各个对象并决定它们之间的依赖关系。而在IOC中,控制对象的创建和管理的不再是程序员自己,而是由容器来完成,即容器来“控制”对象的创建和管理,“反转”了对象的创建和管理的控制权
很明显能看出,IOC的引入,将各组件解耦了。按照传统编码方式,我们在A类中显式的new 出一个B类,如果我们要把B类替换成一个BB类,那么A类这个地方的引用也需要修改,引用B的地方如果很多呢?那岂不是要大面积改动?
你也许会自然而然的想到,难道不能把B类和BB类都实现同一个接口,或者BB类继承自B,然后通过向上转型解决这个问题吗? 很高兴你有这种想法,这种想法是可行的。但从工程角度而言,限制太大,很多类没有接口或无法继承,那么就没法用这个方法了。
那么通过容器解耦到底是怎么实现的呢?其实是通过DI(依赖注入) 解决的
可以看到传统模式,创建A对象,我就得新建个B和C,新建B又得新建C和D,如同套娃。而在IOC模式,创建A时,容器会主动为你寻找并装填B和C,不用你再新建(除非容器里也没有)。而提供B需要的C和D,同样不需要你管,容器会找到C和D,装填在B上,然后又把B为你装填在A上。
这种把A依赖的B和C主动装填上的模式,就叫依赖注入。依赖注入是IOC的一种具体实现,不仅让开发者不用频繁的new对象(尤其是对象还嵌套对象时),而且能减少重复对象,省事且省空间
AOP的基础是动态代理,而动态代理jdk和CGlib都可以实现,也就是说使用原始的java EE也是可以做出切面效果的,但是开发者得手动写大量的代码来进行切面的植入,而且往往还不具备通用性。
Spring则以框架的形式帮你完成了这部分代码,且实现了通用,你只需要按照要求,使用一些切面简单的配置就能完成大面积的切面增强
具体可以参考另一篇文章:Spring核心特性—— AOP(面向切面编程)
我们说spring框架是为了减少传统java EE项目的配置强度和开发强度的,而且spring曾经的介绍是个
可以针对bean的生命周期进行管理的轻量级框架,然而,发展到现今的spring,功能越来越丰富,代码规模也越来越大,它“轻量级”的身份也不再那么贴合了。
此时的spring也逐渐露出诸多问题,由于日渐庞大的功能支持,导致使用者的学习成本大大增加且不说,就连其初衷也渐渐背离了,我们现在来说说spring的一些弊端:
- 配置繁琐,本身spring就是为了减少程序配置而创建的框架,然而发展至今,spring框架要做的功能和兼容也越来越繁杂,导致框架本身的xml配置也越来越多
- 复杂的体系,spring按照模块划分成了多个包,想用事务得引用spring-tx,测试得引用spring-test。对于初学者来说懵懂又麻烦
- 版本依赖,spring版本的升级很频繁,很容易出现spring组件间的版本冲突,需要手动管理各组件的版本依赖
鉴于上文提到的越来越多的spring的弊端,尤其是组件版本与组合的问题。必须要好好管理,由此,在spring的基础上,Pivotal团队提供的全新框架——Spring Boot,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。我们举个配置的例子来详细说明。
传统的spring项目,配置基本以xml为主,比如我们要配置数据源,需要如下配置
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
这是spring自带的数据源类,没有连接池,功能比较薄弱。当然我们可以引入三方包,然后配置成其他数据源类,此处不再赘述。而在springboot中,我们不再以bean的形式维护数据源,而是可以在application.properties里维护
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
细心观察,你会发现,springboot下,我们没有手动维护数据源的类,这是因为springboot2.x默认采用了HikariCP作为数据源技术(之前是org.apache.tomcat.jdbc.pool.DataSource),而springboot又是怎么采用HikariCP作为默认数据源的呢? 我在下面的特性章节会为你解释。
如果你用过spring-boot,不难发现,在springboot下,我们引入的包很多都是带着starter的,比如
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
实际上,这些启动器包大部分本身并不含有代码,而是作为一个依赖管理,为我们引入更具体的其他包。比如spring-boot-starter-jdbc就为我们引入了指定版本的HikariCP组件
当然除了jdbc之外,还有很多启动器包,比如:
- spring-boot-starter-web:包含Spring MVC和Spring WebFlux框架,用于构建Web应用程序。
- spring-boot-starter-amqp:包含Spring AMQP库和RabbitMQ消息代理,用于处理分布式消息传递。
- spring-boot-starter-data-redis:包含Spring Data Redis库,用于处理Redis数据存储和访问。
- spring-boot-starter-test:包含JUnit、Mockito、Hamcrest和Spring Test框架,用于单元测试和集成测试
你或许会问,还是有这么多starter包,如果是新建工程,那不还是得引用很多依赖吗?没错,启动器也是按照模块划分的,并不是一个包就含所有,而是不同模块功能提供一些内置的推荐的组件,让你少点组件版本查询,也少写点些手动依赖。比如说我们说的数据源,它推荐HakariCP,MQ它推荐rabbitMQ。一般在项目初期,如果没有特定组件需求时,采用它默认的就好,后续项目想放弃rabbitMQ,转用rocketMQ,那再替换即可
当我们引入了各类starter包,事情并没有结束,因为是基于spring框架的,很多包引入了以后,我们还需要把加载对应的配置类,然后创建Bean并放入到容器中,对应的功能与配置才算生效
依然以Hakari数据源为例,我们引入了spring-boot-starter-jdbc,也知道该包又引入了Hikari包。那后续呢?这就要提到spring-boot的自动装配功能了。我们把目光放到第一节我们就提及的spring-boot核心包:spring-boot-autoconfigure上
通过这个目录,可以看出,自动装配目前已经覆盖了不少的功能模块,换句话说,你在使用这些功能时,可以享受到自动装配带来的便捷,我们仍用数据源举例。
可以看到,在 DataSourceConfiguration 中,会提供几种数据源,但是因为我们只引了Hikari,所以只有它能被加载,这是 基于其上的 ConditionalOnClass 注解,以及 ConditionalOnProperty 中的matchIfMissing = true参数
@ConditionalOnProperty(
name = "spring.datasource.type",
havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
这两个注解保证了,在有hikari包的情况下,无需任何显式的配置,就可加载DataSourceConfiguration.Hikari。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
如图加载这个类后,能为我们创建个Bean。那么现在我们知道它能被加载,又是谁真正负责加载它的呢?答案是是自动配置类,而自动配置类的加载流程,其实已经超出spring-boot的范畴,回到了spring加载Bean的机制,所以这里长话短说。spring在使用外部jar包时,会检查jar包里的spring.factories,扫描并加载这文件里提到的类。
如图,加载了DataSourceAutoConfiguration类,而在该自动配置类中,又实用 import 注解引入了这几种数据源类。当然,我们从上面知道,import虽然是import了,但这几个数据源类上又各自有注解,导致最后其实只有hikari成功注册了Bean
除了上面提及的一些配置上的自动化,spring备受欢迎的更大原因,是因为其可以内置Tomcat、Jetty或Undertow(无需部署WAR文件),并提供一些默认配置,免去了开发者手动安装和配置应用服务器的烦恼。这在提倡微服务的年代,可谓是如虎添翼
关于spring-boot内嵌服务器的原理,是一个比较庞大的内容,我们后续会专门讲解。
关联:
- 功能几乎一致:Spring Boot是基于Spring框架,因此可以使用Spring框架提供的所有功能
- 目的一致:简化开发配置,spring是为了简化原始的javaEE模式,而spring-boot则是为了简化spring项目的搭建和配置
区别:
- Spring需要手动引入各种依赖库,并一一确定版本,而Spring Boot则通过各类启动器自动引入依赖库来简化应用的构建,且启动器会帮你确定好匹配的各组件版本。
- Spring依赖于外部的配置文件(如XML、Java Config等),而Spring Boot提供了“约定大于配置”的方式,可以通过默认约定处理常见的配置需求
- Spring Boot可以内嵌应用服务器,独立部署与执行,而spring项目则需要外部提供应用服务器
一句话总结:
早期javaEE项目是农村自建住宅,都是房子,但实现与样式各有区别,非常混乱;
spring则是提供了一个标准房图纸,使用它能快速辅助你搭建出一个标准房型,但房子很多细节,比如桌椅门窗仍然需要你自己敲定;
spring-boot则直接是精装商品房,不仅房子是按标准图纸搭建,桌椅门窗等装修也是有默认的一套,可以直接为你装上。
- 点赞
- 收藏
- 关注作者
评论(0)