从使用传统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 进行初始化,也就是约定优于配置,然后有的小伙伴就会问,那么什么是约定优于配置呢?

约定优于配置可以这样理解:

  1. 开发人员仅需要规定应用中不符合约定的部分。
  2. 在没有规定配置的地方,采用默认配置

举例说明:

不符合规定的部分:例如,如果模型中有个名为 User 的类,那么数据库中对应的表就会默认命名为 user。只有在偏离这一约定时,例如将该表命名为 “user_info”,才需写有关这个名字的配置。

规定默认配置的地方:

  1. Maven 的目录结构。项目创建后默认有 resources 文件夹,用于存放资源配置文件。默认的编译生成的类都在targe文件夹下面。
  2. Spring Boot默认的配置文件必须是,也只能是后缀名为 yml 或者 properties 的文件,且名称唯一。
  3. 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

image-20201122204029210

其他的大家可以自行尝试看一下,额外需要注意的是 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 包:

starterdesc
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 中读取配置文件有三种方式:

  1. Environment

  2. @Value

  3. @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 的知识点可以说太多太多了,文中只是把自己能想到的几点描述了出来。

欢迎大家在留言区互动,

博客园持续更新,欢迎关注。希望这篇文章对大家有所帮助。

博客园:https://www.cnblogs.com/niceyoo

posted @ 2020-11-23 00:41  niceyoo  阅读(2068)  评论(0编辑  收藏  举报