前面提到,Spring家族拥有多款开源框架,开发人员可以基于这些框架实现各类Spring应用程序。在本节中,我们介绍基于Spring Boot开发面向Web场景的服务,这也是互联网应用程序最常见的表现形式。
在日常开发过程中,构建典型的Web应用程序通常包含三个维度的工作。首先,从纵向的角度看,可以把Web应用程序分成三个主要的层次,即数据访问(Repository)层、业务逻辑(Service)层和Web层。其次,从横向的角度看,又需要考虑系统的监控、安全等基础设施类需求。最后,Web应用程序并不一定独立运行,而可能需要与其他系统之间进行有效的集成。这样,我们可以把一个Web应用程序进行拆解,如图1-3所示。
图1-3 Web应用程序的拆分维度示意图
显然,图1-3中的每一个组成部分都需要采用专门的技术和工具分别实现。只有将这些技术和工具有机地结合在一起,才能让这个应用程序完整地运行起来。在这个过程中,我们就不得不考虑如下典型问题。
上述问题集中暴露了传统Spring框架缺乏高效性和简单性的问题。针对系统开发过程中存在的上述问题,在技术体系的演进上,基于传统Spring框架的Spring Boot诞生了。Spring Boot本质上是对传统Spring框架的封装和扩展,其整体架构如图1-4所示。
图1-4 Spring Boot整体架构
从图1-4中可以看到,在传统Spring框架所提供的各项开发功能的基础上,Spring Boot还提供了一些特有组件来简化开发过程,包括:
Spring Boot大幅降低了常见开发场景的实现难度,同时还充分考虑了安全性、测试性等一系列非功能开发需求。
针对一个基于Spring Boot开发的Web应用程序,其代码组织方式需要遵循一定的项目结构。在本小节中,我们将给出这一项目结构,并提供一个可以直接运行的Spring Boot Web应用程序示例。
在本书中,如果不特殊说明,我们都将使用Maven来管理代码工程的结构和包依赖。一个典型的Web应用程序的项目结构如下所示:
demo-service
src/main/java
com.spring.demo
DemoApplication.java →启动类
com.spring.demo.controller →控制器组件
com.spring.demo.repository →数据访问层组件
com.spring.demo.service →业务逻辑层组件
com.spring.demo.domain →领域实体
src/main/resources
application.yml →配置文件
pom.xml →包依赖
这里有几个方面需要特别注意,分别是包依赖、启动类、控制器组件以及配置文件,下面分别展开介绍。
(1)包依赖
Spring Boot提供了一系列starter工程来简化各种组件之间的依赖关系。以开发Web服务为例,需要引入spring-boot-starter-web这个工程。在应用程序中引入spring-boot-starter-web组件就像引入一个普通的Maven依赖一样,如代码清单1-1所示。
代码清单1-1 将spring-boot-starter-web依赖包引入代码
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
一旦spring-boot-starter-web组件引入完毕,我们就可以充分利用Spring Boot提供的自动配置机制开发Web应用程序。请注意,spring-boot-starter-web包的命名上有个特殊的地方,即使用了-starter,这是Spring Boot所特有的starter机制的标识。Spring Boot中的starter是一种非常重要的机制,可以将以前繁杂的配置统一集成进starter,开发人员只需要在Maven中引入starter依赖,Spring Boot就能自动扫描要加载的信息并启动相应的默认配置。我们在本书第13章中会对Spring Boot Starter展开详细的介绍。
我们来看一下spring-boot-starter-web工程的组成部分,可以看到这个工程中并没有具体的代码,只是包含了一些pom依赖,如下所示:
org.springframework.boot:spring-boot-starter
org.springframework.boot:spring-boot-starter-tomcat
org.springframework.boot:spring-boot-starter-validation
com.fasterxml.jackson.core:jackson-databind
org.springframework:spring-web
org.springframework:spring-webmvc
可以看到这里包括了传统Spring WebMVC应用程序中会用到的spring-web和spring-webmvc组件,因此Spring Boot在底层实现上还是基于这两个组件完成了对Web请求的响应流程的构建。
本书将采用最新的Spring Boot 2.5.3版本,我们发现它所依赖的Spring组件都升级到了5.3.9版本,如图1-5所示。
图1-5 Spring Boot 2.5.3版本的依赖包示意图
(2)启动类
使用Spring Boot最重要的一个步骤是创建一个Bootstrap启动类。Bootstrap类结构简单且比较固化,如代码清单1-2所示。
代码清单1-2 Spring Boot Bootstrap类示例代码
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
显然,这里引入了一个全新的注解@SpringBootApplication。在Spring Boot中,添加了该注解的类就是整个应用程序的入口,一方面会启动整个Spring容器,另一方面会自动扫描代码包结构下的@Component、@Service、@Repository、@Controller等注解,把这些注解对应的类转化为Bean对象并全部加载到Spring容器中。
(3)控制器组件
Bootstrap类为我们提供了Spring Boot应用程序的入口,相当于应用程序已经有了最基本的骨架。接下来我们就可以添加针对HTTP请求的访问入口,表现在Spring Boot中也就是添加一系列的Controller类。这里的Controller与Spring WebMVC中的Controller在概念上是一致的,一个典型的Controller类如代码清单1-3所示。
代码清单1-3 Controller类示例代码
@RestController @RequestMapping(value="users") public class UserController { @GetMapping(value = "/{id}") public User getUserById(@PathVariable Long id) { User user = new User(); user.setId(id); user.setName("Tianyalan"); user.setPassword("123456"); return user; } }
请注意,这里为了演示方便,使用硬编码完成了一个HTTP GET请求的响应处理。上述代码中包含了@RestController、@RequestMapping和@GetMapping这三个注解。其中,@RequestMapping注解用于指定请求地址的映射关系,@GetMapping注解的作用等同于指定了GET请求的@RequestMapping注解。而@RestController注解是传统Spring MVC中所提供的@Controller注解的升级版,相当于@Controller和@ResponseEntity注解的结合体,会自动使用JSON实现序列化/反序列化操作。
(4)配置文件
最后,我们看一下配置文件。请注意配置文件可以是空的,开发人员如果不需要特别指定服务器端口的信息,那么完全可以基于Spring Boot内置的默认配置来运行Web应用程序。默认情况下,Spring Boot会使用8080作为监听HTTP请求的服务端口。
Spring Boot中的配置体系非常有特色,充分采用了“约定优于配置”(Convention Over Configuration)这一设计理念。作为专题,我们将在1.2.3节中具体讲解该内容。
基于Spring Boot创建Web应用程序的方法有很多,其中最简单、最直接的方法就是使用Spring官方提供的Spring Initializr模板。直接访问Spring Initializr网站(http://start.spring.io/),选择创建一个Maven项目并指定相应的Group和Artifact,然后在添加的依赖中选择Spring Web,点击GENERATE按钮即可。界面效果如图1-6所示。
图1-6 使用Spring Initializr创建Web应用程序示意图
当然,对于有一定开发经验的人员而言,完全可以基于Maven本身的功能特性和结构来生成图1-6中的代码工程。
接下来,让我们来为这个代码工程添加一些RESTful风格的HTTP端点。这里,直接使用前面已经构建完成的UserController。
现在RESTful端点已经开发完成,我们需要对这个应用程序进行打包。基于Spring Boot和Maven,当我们使用mvn package命令构建整个应用程序时,将得到一个userservice-0.0.1-SNAPSHOT.jar文件。而这个JAR文件就是可以直接运行的可执行文件,内置了Tomcat Web服务器。也就是说,我们可以通过如代码清单1-4所示的命令直接运行这个Spring Boot应用程序。
代码清单1-4 Spring Boot应用程序启动命令
java -jar userservice-0.0.1-SNAPSHOT.jar
那么,如何验证服务是否启动成功,以及HTTP请求是否得到正确响应呢?在本书中,我们将引入Postman来演示如何通过HTTP暴露的端点进行远程服务访问。Postman提供了强大的Web API和HTTP请求调试功能,界面简洁明晰,操作也比较方便快捷和人性化。Postman能够发送任何类型的HTTP请求(如GET、HEAD、POST、PUT等),并能附带任何数量的参数和HTTP请求头(Header)。
通过Postman访问http://localhost:8080/users/1端点,我们可以得到如图1-7所示的HTTP响应结果,说明整个服务已经启动成功。
图1-7 通过Postman访问HTTP端点效果示意图
现在我们已经明白如何构建、打包以及运行一个简单的Web应用程序。这是一切开发工作的起点,后续所有的案例代码都将通过这种方式展现在你面前,包括接下来要介绍的Spring Boot配置体系。
通过前面的分析,我们可以看到在开发Web应用程序时,使用Spring Boot比Spring WebMVC更为简单高效。你可能已经注意到了,以往在使用Spring WebMVC时需要指定的数据库访问、Web服务端点等一系列配置信息都不见了。我们只需要在Maven中添加一项spring-boot-starter-web依赖并实现一个Controller,就可以提供微服务架构中所推崇的RESTful风格的接口。这需要引入Spring Boot中最具创新性的一项功能,即自动配置机制。事实上,如果没有特殊的配置需求,开发人员完全可以基于Spring Boot内置的配置体系完成诸如数据库访问等功能的相关配置信息的自动集成。
在Spring Boot中,对配置信息的管理采用约定优于配置的设计理念。在这一理念下,开发人员所需要设置的配置信息数量比起使用传统Spring框架将大大减少。那么,Spring Boot是如何组织和管理这些配置信息的呢?
为了达到集中化管理的目的,Spring Boot对配置文件的命名也做了一定的约定,分别使用label和profile来指定配置信息的版本以及运行环境。其中label表示配置版本控制信息,而profile则用来指定该配置文件所对应的环境。在Spring Boot中,配置文件同时支持.properties和.yml两种文件格式,结合label和profile的概念,如下所示的配置文件命名都是常见和合法的:
/{application}.yml
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
Yaml这种标记语言的语法和其他高级语言类似,并且可以非常直观地表达各种列表、清单、标量等数据形态,特别适合用来表达或编辑数据结构和各种配置文件。例如,我们可以指定如代码清单1-5所示的数据源配置,这里使用了.yml文件。
代码清单1-5 基于.yml文件的数据源配置示例
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/user username: root password: root
如果采用.properties配置文件,那么上述配置信息将表现为如代码清单1-6所示的形式。
代码清单1-6 基于.properties文件的数据源配置示例
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/user spring.datasource.username=root spring.datasource.password=root
显然,类似这样的数据源通常会根据环境的不同而存在很多套配置。通常,推荐的做法是为每个不同的环境提供一个独立的配置文件。例如我们可以分别针对prod、test、uat等环境提供对应的配置文件,如下所示:
application-prod.properties
application-uat.properties
application-test.properties
application.properties
注意这里有一个没有添加任何环境后缀的application.properties配置文件。在Spring Boot中,这个application.properties就是主配置文件,是所有配置信息管理的入口。Spring Boot在获取配置信息时,会先从这个主配置文件中读取相应的配置。因此,我们可以把那些适用于所有环境的全局配置信息放在application.properties中。
另外,在Spring Boot中,我们可以在主配置文件中使用如代码清单1-7所示的配置方式来激活当前所使用的Profile。
代码清单1-7 激活当前所使用的Profile配置示例
spring.profiles.active = prod
上述配置项意味着系统在读取完application.properties中的全局配置信息之后会继续读取application-prod.yml配置文件中的配置内容。
如果我们不希望在全局配置文件中指定所需要激活的Profile,而是想把这个过程延迟到运行这个服务时,那么可以直接在java -jar命令中添加--spring.profiles.active参数,如代码清单1-8所示。
代码清单1-8 通过java -jar命令激活Profile代码示例
java -jar userservice-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
这种实现方案在通过脚本进行自动化打包和部署的场景下非常有用。
接下来,我们给出一些常见开发场景的配置示例。对于一个Web应用程序而言,最常见的配置可能就是指定服务暴露的端口地址,如代码清单1-9所示。
代码清单1-9 服务暴露的端口地址配置示例
server: port: 8080
同时,数据库访问也是Web应用程序的基本功能。因此,关于数据源的设置也是一种常见的配置场景,我们已经在前面给出了一个基本的示例。这里再以JPA为例,给出如代码清单1-10所示的一种配置方案。
代码清单1-10 数据源配置示例
spring: jpa: hibernate: ddl-auto: create show-sql: true
显然,这里使用了Hibernate作为JPA规范的实现框架,并设置了show-sql等相关属性。
最后,开发人员一般需要对日志级别和对象进行设置,代码清单1-11所示的就是一个典型的配置示例。
代码清单1-11 日志配置示例
logging.level.root=WARN logging.level.com.springboot.user=INFO
我们设置了系统的全局日志级别为WARN,而针对自定义的com.springboot.user包下的日志,则将其级别调整到INFO。