从使用传统Web框架到切换到Spring Boot后的总结
1、前言
其实我接触 Spring Boot 的时间并不长,所以还算一个初学者,这篇文章也算是我对 Spring Boot 学习以及使用过程中的复盘,如果文章出现描述错误或表达不清晰的地方,欢迎大家在评论区留言互动。
没想到 Spring Boot 这两年竟然普及的这么快, 两年前刚毕业的时候,因为待的是二线城市的小公司,公司的技术栈并不追求新颖,而是追求稳定、能用就行的理念,所以项目上就一直使用传统的 SSM、SSH。
当搭建后端框架时,需要手动添加 Maven 配置、各种 XML 配置,反正就是一个项目创建就要配置一遍,从我两年前写的这篇 SSM框架搭建,就可以看出该过程是有多么的繁琐,甚至有时候因为配置错误以致于一上午就又可以愉快的划水了...
而项目部署时也是头大,首先需要安装 Tomcat,然后将项目编译打包成 war 包,再将 war 包放在 Tomcat 的 webapp 目录下部署运行,这个过程就觉得很不方便...
汇总一下构建一个传统项目需要的步骤:
- 配置 web.xml,加载 Spring 和 Spring MVC
- 配置数据库连接、配置 Spring 事务
- 配置加载配置文件的读取,开启注解
- 配置日志文件
- Redis、MQ 等等 …
- 配置完成之后部署 Tomcat 调试
- 可能还需要考虑各个版本的兼容性,jar 包冲突的各种可行性。
因为如上种种问题,当一接触到 Spring Boot 后就被它简单的操作吸引了...
真羡慕现在的小伙伴,一上来就是用的 Spring Boot 了~ 不用再像我那会一样。
2、Spring Boot 解决的问题
Spring Boot 的出现大大简化了传统 Web 框架的搭建过程,提高了开发效率,Spring Boot 总结后有以下几个特点:
- 可以快速的创建 Spring 应用。
- 使用嵌入式的Servlet容器,应用无需打成WAR包
- 整合了大量第三方框架,做到开箱即用。
- 约定大于配置。
这几个特点基本上也是面试 Spring Boot 时常问的,也正是因为这几个特点让之前繁琐的搭建过程变的简单。
我们都知道,特点并不是解决问题的关键,所以我们要了解,Spring Boot 到底是如何解决问题的。
2.1、自动配置
之前我们使用 XML 方式时,有相当大的部分就是对 Bean 进行初始化并配置,比如下面这一段就是配置数据库连接信息:
<!-- 配置 数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://10.211.55.4:3306/test" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
而使用 Spring Boot 后,会根据某些约定的规则对所有配置的 Bean 进行初始化,也就是约定优于配置,然后有的小伙伴就会问,那么什么是约定优于配置呢?
约定优于配置可以这样理解:
- 开发人员仅需要规定应用中不符合约定的部分。
- 在没有规定配置的地方,采用默认配置
举例说明:
不符合规定的部分:例如,如果模型中有个名为 User 的类,那么数据库中对应的表就会默认命名为 user。只有在偏离这一约定时,例如将该表命名为 “user_info”,才需写有关这个名字的配置。
规定默认配置的地方:
- Maven 的目录结构。项目创建后默认有 resources 文件夹,用于存放资源配置文件。默认的编译生成的类都在targe文件夹下面。
- Spring Boot默认的配置文件必须是,也只能是后缀名为 yml 或者 properties 的文件,且名称唯一。
- application.yml中默认的属性。数据库连接信息必须是以 spring: datasource: 为前缀;再就是其他环境配置,比如端口号、请求路径等,后面单独写一篇文章关于默认配置文件的。
是否对这个 yml 或者 properties 文件中配置了信息就实现了配置有点好奇?
我们还是以数据库连接信息为例:在 Spring Boot 中有一个 DataSourceAutoConfiguration 配置类,这个类会自动查找 application.yml 或者 properties 文件里的 spring.datasource.* 路径,然后相关属性会自动配置到数据源中。这个过程就属于约定大于配置,如果感兴趣的小伙伴可以进入这个类看看。
2.2、内嵌容器
Spring Boot 应用程序可以不用部署到外部容器中,比如 Tomcat。Spring Boot 应用可以直接通过 Maven 命令将项目编译成可执行的 jar 包,通过 java -jar 命令启动即可,非常方便。
怎么不需要 Servlet 容器就启动起来了呢?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
只要加上这个依赖,Spring Boot 就帮我们默认内嵌了 Tomcat,当然同样也支持修改,比如可以使用 jetty(见标签7修改默认内置的Tomcat容器)。
内嵌就内嵌了吧,简单看一下 Tomcat 在 Spring Boot 中是如何启动的。
可以看一下我写的这篇文章:https://www.cnblogs.com/niceyoo/p/14019428.html
2.3、应用监控
应用监控是项目部署中必不可少的环节,以前我们怎么知道系统实际运行的情况呢?
需要我们人为的进行运维监控,比如对 cpu、内存、数据库连接等监控。如果是对系统接口,那么可能会单独写个测试接口来判断当前应用的健康程度,当然,大部分情况接口只要返回200就认为当前应用是健康的。
但是这种情况会存在很大的问题,首先前者会浪费人力资源,后者则因为固定接口形式无法真正意义上判断应用的健康状态。
而 Spring Boot 中提供了一个用于监控和管理自身应用信息的模块—Actuator,通过 Actuator,可以实现对程序内部运行情况进行监控,比如 Bean 加载情况、环境变量、日志信息、线程信息等。当然也可以自定义跟业务相关的监控,通过 Actuator 的端点信息进行暴露。这个有点 DevOps 的意思。
如何集成到 Spring Boot 中呢?
只需要在 pom.xml 中添加如下依赖即可:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
应用启动则直接可以访问:
- http://localhost:port/actuator 查看所有端点信息。
- http://localhost:port/actuator/env 查看该应用全部环境属性。
application配置文件中可配置的参数:
management:
endpoints:
web:
# actuator的访问路径,替换默认/actuator
base-path: /monitor
# 设置是否暴露端点 默认只有health和info可见
exposure:
# include: env # 方式1: 暴露端点env,配置多个以,隔开
include: "*" # 方式2: 包括所有端点,注意需要添加引号
# 排除端点,如果不排除则只需要访问该应用的/shutdown 端点就能实现关闭该应用的远程操作
exclude: shutdown
server:
port: 8888 #新开监控端口,不和应用用同一个端口,
endpoint:
health:
show-details: always # 显示db、redis、rabbti连接情况等
shutdown:
enabled: true #默认情况下,除shutdown以外的所有端点均已启用。手动开启
按照如上配置则访问路径为:http://localhost:8888/monitor
我们可以看到有很多节点(图中省略),这些结点被称作 端点,如下是这些端点的描述:
ID | 描述 | 默认启用 | 默认公开 |
---|---|---|---|
auditevents |
公开当前应用程序的审计事件信息 | Yes | No |
beans |
显示应用程序中所有Spring bean的完整列表 | Yes | No |
conditions |
显示在配置和自动配置类上评估的条件以及它们是否匹配的原因 | Yes | No |
configprops |
显示所有@ConfigurationProperties 对照的列表 |
Yes | No |
env |
从Spring的ConfigurableEnvironment 中公开属性 |
Yes | No |
flyway |
显示已应用的任何Flyway数据库迁移 | Yes | No |
health |
显示应用程序健康信息 | Yes | Yes |
httptrace |
显示HTTP跟踪信息(默认情况下,最后100个HTTP请求-响应交互) | Yes | No |
info |
显示任意应用程序信息 | Yes | Yes |
loggers |
显示和修改应用程序中记录器的配置 | Yes | No |
liquibase |
显示已应用的任何Liquibase数据库迁移 | Yes | No |
metrics |
显示当前应用程序的“指标”信息 | Yes | No |
mappings |
显示所有@RequestMapping 路径对照的列表 |
Yes | No |
scheduledtasks |
显示应用程序中调度的任务 | Yes | No |
sessions |
允许从Spring Session支持的会话存储中检索和删除用户会话 | Yes | No |
shutdown |
让应用程序优雅地关闭 | No | No |
threaddump |
执行线程转储 | Yes | No |
如上所知,如果想显示应用程序健康信息,那么就访问 health 端点,即:
http://127.0.0.1:8888/monitor/health
其他的大家可以自行尝试看一下,额外需要注意的是 shutdown 端点,项目中一定要排除,否则只需要访问该应用的 /shutdown 端点就能实现该应用的远程关闭操作,十分危险。
这就完了?
显然不是,上边这样直接访问端点,然后返回 JSON 数据,显然很不直观,毕竟现在很流行可视化嘛~ 咳咳。
所以这时候我们可以通过 Spring Boot Admin 来对 actuator 返回的这些数据进行整理。
关于 Spring Boot Admin 的介绍:
Spring Boot Admin 是用于管理和监控 Spring Boot 应用程序运行状态的。在 Spring Boot 项目中可以通过集成 Spring Boot Admin Client 向 Spring Boot Admin Server 进行注册(通过 HTTP),这样我们就可以在 Spring Boot Admin Server 统一管理 Spring Boot 应用。可视化是端点之上的 Vue 项目。
提供功能如下:
- 显示健康状态及详细信息,如JVM和内存指标、数据源指标、缓存指标
- 跟踪并下载日志文件
- 查看jvm系统-和环境属性
- 查看Spring启动配置属性
- 方便loglevel管理
- 查看线程转储
- 视图http-traces
- 查看http端点
- 查看计划任务
- 查看和删除活动会话(使用spring-session)
- 状态更改通知(通过电子邮件、Slack、Hipchat…)
- 状态变化的事件日志(非持久性)
- ……
使用 Spring Boot Admin 也是非常的简单,直接添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.3.0</version>
</dependency>
配置文件如下:
spring:
boot:
admin:
# 修改上下文路径
context-path: /admin
client:
url: http://127.0.0.1:${server.port}/admin
再在启动类上加上 @EnableAdminServer 注解,齐活,完事。
通过如上界面,我们可以清楚的看到应用的名称和 IP 端口信息,应用名称是默认的,也可以在配置文件中通过 spring.application.name 来自定义名称。
我们还可以点击服务信息进去查看详情,左边是对应的功能菜单,右边是数据展示的页面。详情中有健康信息、线程信息、JVM 内存等信息,都通过图形的方式展示,一目了然。
通过左侧功能菜单可以看到还有 日志、JVM、缓存 等管理功能,大家可以点点看看。
2.4、Spring Boot Starter 开箱即用
关于 Spring Boot Starter 相信大家一定不陌生,很多第三方工具的引入都是涉及到 Starter 包,Starter 包可以说是 Spring Boot 中的核心功能,Starter 的出现,简化了 Spring 很多工作。不懂就问环节:什么是 Starter?
大家可以看一下我转载的这篇文章:
https://www.cnblogs.com/niceyoo/p/14022406.html
总之,Starter 包简化框架集成难度,将 Bean 的自动装配逻辑封装在 Starter 包内部,同时也简化了 Maven Jar 包的依赖,对框架的集成只需要加入一个 Starter 包的配置,降低了烦琐配置的出错几率。
如下是 Spring Boot 提供的开箱即用 Starter 包:
starter | desc |
---|---|
spring-boot-starter-web |
用于快速构建Web,包含 RESTful 风格框架、SpringMVC和默认的嵌入式容器Tomcat |
spring-boot-starter-test |
用于测试 |
spring-boot-starter-data-jpa |
带有Hibermate的Spring Data JPA,用于操作数据库。 |
spring-boot-starter-jdbc |
传统的JDBC支持 |
spring-boot-starter-thymeleaf |
支持Thymeleaf模板 |
spring-boot-starter-mail |
支持Java Mail、Spring Email 发送邮件 |
spring-boot-starter-integration |
Spring框架创建的一个API,面向企业应用集成(EAI) |
spring-boot-starter-mobile |
SpringMVC的扩展,用来简化手机上的Web应用程序开发 |
spring-boot-starter-data-redis |
快速整合并操作 Redis:通过Spring Data Redis、Redis Client使用Redis |
spring-boot-starter-validation |
Bean Validation是一个数据验证的规范,Hibernate Validator是一个数据验证框架 |
spring-boot-starter-websocket |
相对于非持久的协议HTTP,Websocket 是一个持久化的协议 |
spring-boot-starter-web-services |
SOAP Web Services |
spring-boot-starter-hateoas |
为服务添加HATEOAS功能 |
spring-boot-starter-security |
用Spring Security进行身份验证和授权 |
spring-boot-starter-data-rest |
用Spring Data REST公布简单的REST服务 |
3、Spring Boot 项目的创建方式
创建 Spring Boot 有两种方式:
- 手动创建一个 Maven 项目,然后添加 Spring Boot 需要的依赖;
- 通过 Spring Initializr 脚手架创建;
工具环境 IDEA,下文是基于 Spring Initializr 脚手架创建。
通过 IDEA 创建 Spring Boot 项目实在是太简单了:
关于下一步之后,无非就是选择创建项目的方式(Maven/Gradle),Spring Boot 版本(2.2.2.RELEASE),引入的依赖(Web/SQL/NoSQL/Security等),在这就不赘述了。
4、Spring Boot 编译打包
Spring Boot 项目打包无非就 「打 jar 包、打 war 包」 这两种情况,这两者的应用场景不同,各有优缺点。
jar 包部署优点:
- 无须搭建本地web容器,默认内置 Tomcat 容器,可以直接以 java -jar 形式运行;
- 因为自带web容器,可以避免由于web容器的差异造成不同环境结果不一致问题。
- 借助容器化,可以进行大规模的部署。
jar 包部署缺点:
- jar 应用体积过大「可以参考瘦身指南」
- 数据源无法通过界面进行管理。
- 修改web容器相关配置比较困难,需要借助代码实现。
war 包部署优点:
- 可以借助web容器管理界面对应用进行管理。
- web容器配置较为灵活,配置和程序分离。「jar包方式其实也可以」
- 应用体积较小,甚至可以借助web容器的包管理功能进一步减小应用大小。
war 包部署缺点:
- 本地需要搭建web容器
- 调试较为困难,需要借助web容器。
- 部署较为困难
至于最终选择哪个,本文不追究,毕竟都是要根据实际项目来说的。
以打 jar 包为例:
首先需要在 pom.xml 文件中配置 spring-boot-maven-plugin 打包插件,也就是我们通常看到的:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这样就可以在 Tmerinal 控制台通过 maven 命令进行打包了:mvn package
或者是可以直接在 IDEA 右侧的 Maven 标签:
有小伙伴可能好奇,难道不需要设置 <packaging>jar</packaging>
吗?
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<packaging>jar</packaging>
...
</project>
其实默认的 就是 jar 方式。
5、Spring Boot 多环境配置
通常在实际开发中,往往涉及到好几个环境,比如开发环境、本地环境、测试环境等等,通常不同环境下的配置信息是不一样的「比如端口号?数据库连接信息等」,因为我们每次只能使用一个配置环境,如果频繁的修改配置以达到效果,自然是麻烦的不行,且很容易出错。
而在 Spring Boot 中,我们可以通过 spring.profiles.active 来激活对应的配置文件。
比如创建如下三个文件:
application.yml「主文件」
- application-dev.yml:开发环境
- application-local.yml:本地环境
- application-test.yml:测试环境
我们想要使用 dev 开发环境,只需要在 application.yml 中使用 spring.profiles.active=dev 就可以完成指定:
spring:
profiles:
active: dev
6、替换内置的 Tomcat 容器
尽管Spring Boot内置了 Tomcat ,但是在高并发场景下的 Tomcat 相对来说比较弱。比如在相同的机器配置下,模拟相等的请求数,Undertow在性能和内存使用方面都是最优的。并且Undertow新版本默认使用持久连接,这将会进一步提高它的并发吞吐能力。所以,如果是高并发的业务系统,Undertow 是最佳选择。
问题是怎么替换?
因为 spring-boot-starter-web 中默认自带的容器是 Tomcat,如果想要替换成 Undertow 也是非常简单的。
首先需要排除 spring-boot-starter-web 包中的 Tomcat,然后单独增加 undertow 容器的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除Tomcat依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 Undertow依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
7、配置文件的读取
以前读取 spring 的配置文件,都是通过工具类类读取,其实无非就是读取 xml 文件,比如,通过 ClassPathXmlApplicationContext 加载到文件,然后再通过 上下文拿到 对应的Bean,再取参等。
而在 Spring Boot 中读取配置文件有三种方式:
-
Environment
-
@Value
-
@ConfigurationProperties
我们一一看看这三种方式:
application.yml 中的模拟数据:
niceyoo:
name: 张三
age: 24
7.1、Environment
Environment 用于管理当前的应用环境信息,定义了获取 Profile 的方法,同时继承了 PropertyResolver,PropertyResolver 提供了属性访问的相关方法。
也就是我们可以使用 Environment 的 getProperty() 方法读取指定配置 Key 的内容。
代码中体现:
@Controller
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private Environment environment;
@PostMapping("/test")
@ResponseBody
public void test(@RequestBody User user) {
String name = environment.getProperty("niceyoo.name");
Integer age = Integer.valueOf(environment.getProperty("niceyoo.age"));
System.out.println(name+" " +age);
}
}
7.2、@Value
@Value 方式就简单多了,直接在接收的属性上加上该注解即可:
@Controller
@RequestMapping(value = "/test")
public class TestController {
@Value("niceyoo.name")
private String name;
@Value("niceyoo.age")
private Integer age;
@PostMapping("/test")
@ResponseBody
public void test(@RequestBody User user) {
System.out.println(name+" " +age);
}
}
7.3、@ConfigurationProperties
使用该注解可以直接注入到实体类中,方便值的同一管理。
比如我们创建一个 Model 实体类,定义 name、age 属性,然后实现 get\set 方法,再在实体类上加上 @Configuration 和 @ConfigurationProperties(prefix="niceyoo") 注解,并指定前缀为 niceyoo。
@Configuration
@ConfigurationProperties(prefix="niceyoo")
public class Model {
private String name;
private Integer age;
省略get、set方法
}
这样就可以将 niceyoo 下面的值直接对应到实体上了,其中加入 @Configuration 注解,我们在使用时可以直接通过 @Autowired 进行注入。
取值代码:
@Controller
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private Model model;
@PostMapping("/test")
@ResponseBody
public void test(@RequestBody User user) {
System.out.println(model.getName()+" " +model.getAge());
}
}
总结
以前 Spring 主打轻量级应用框架,但随着外界不断地进行扩充,像是 Shiro、Security、MQ、Redis、ElasticSearch 等等等等, 总之就是 Spring 几乎可以做任何事了,但是相应的问题也来了。
Spring 每集成一个第三方软件,就需要手动增加一些配置,随着新软件的加入,以至于需要引入越来越多的配置,且这些配置各玩各的,难以理解,难以管理,我记得我实习时的那家公司就是这样,各种五花八门的配置文件,以至于经常配置出错,然后解决配置相关的问题就需要好久。
工欲善其行,必先利其器。
Spring Boot 的出现可以说真正的颠覆了以前传统的 Web 项目,开发人员再也不用配置繁琐的 xml 文件了,简直是解放了双手,整个项目的配置文件也变得简洁了。
正所谓简洁并不意味着简单,Spring Boot 只是将众多复杂的功能进行了封装,让我们在使用的时候足够的简单。
关于 Spring Boot 的知识点可以说太多太多了,文中只是把自己能想到的几点描述了出来。
欢迎大家在留言区互动,
博客园持续更新,欢迎关注。希望这篇文章对大家有所帮助。