使用 Spring Boot 2 构建微服务
1. Spring Boot 执行方法
Spring Boot 可以通过以下方法执行:
- 使用可执行 JAR 文件,通过 $ java -jar 命令,搭配以下嵌入式 Servlet 容器:
- Tomcat 8.5
- Jetty 9.4
- Undertow 1.4
- 通过传统 WAR 部署到任何实现 Servlet 3.1+ 规范的应用服务器或 Servlet 容器中。
最新版本(撰写本文时为 v2.0.5)支持使用 Maven 或 Gradle 进行构建,运行需要 Java 8 或更高版本,并且基于 Spring 5.0.9 版本作为核心。
2. Maven 设置
Apache Maven 是最常见的构建管理系统,可作为 Java 应用程序的事实上的构建和打包操作工具。Spring Boot 与 Apache Maven 3.2 或更高版本兼容,为了轻松管理所有 Spring Boot 依赖项(特别是正确的版本),可以将 Maven POM 文件设置为继承自 spring-boot-starter-parent 项目。
以下是一个 pom.xml 文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
使用以下命令构建和打包应用程序:
$ mvn clean package
3. Gradle 设置
Gradle 是一个开源的构建自动化工具,使用 Groovy 或 Kotlin DSL 编写的脚本。它得到了主要 IDE 的支持,可以使用命令行界面或通过持续集成服务器运行。
安装后,在项目根目录下运行以下命令创建新项目(或自动将现有 Maven 项目转换为 Gradle 项目):
$ gradle init
为了在 Gradle 项目中使用 Spring Boot,可以创建如下 Gradle 文件:
plugins {
id 'org.springframework.boot' version '2.0.5.RELEASE'
id 'java'
}
jar {
baseName = 'myproject'
version = '0.0.1-SNAPSHOT'
}
repositories {
jcenter()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-dependencies:2.0.5.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
构建可执行 JAR 文件:
$ ./gradlew mySpringBootJar
运行可执行 JAR 文件:
$ java -jar build/libs/gradle-my-spring-boot-project.jar
或者使用以下 Gradle 命令运行:
$ ./gradlew bootRun
4. 从早期版本升级 Spring Boot
Spring Boot 提供了一项功能,使开发人员能够在应用程序启动时分析应用程序环境并打印结果。还可以使用属性迁移器启动器自动迁移应用程序属性。
要激活此环境,需在项目中添加以下 Maven 依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
完成分析和迁移后,确保从项目依赖项中移除该模块。
5. 构建 Spring Boot 微服务
这里将实现一个足球运动员微服务,它将处理足球运动员领域,暴露 CRUD API,并使用 PostgreSQL 数据库存储和检索信息。
构建应用程序需要以下工具及安装信息:
| 工具名称 | 安装信息 |
| ---- | ---- |
| Apache Maven 3.5.4 | 安装链接 |
| JDK 1.8.0_171 | 可使用 Oracle JDK 或 OpenJDK,推荐 OpenJDK 安装链接 |
| Spring Boot 2.0.5 | 后续介绍安装说明 |
| Docker Community Edition 18.06.1 | 安装链接 |
6. 项目详情
构建微服务源代码需要在系统上安装 PostgreSQL,这里使用 Docker 来安装和管理 PostgreSQL。
以下是安装和配置 PostgreSQL 的步骤:
1. 安装 Docker 后,打开新的终端窗口,运行以下命令启动 PostgreSQL 容器:
$ docker run --name postgres_springboot -e POSTGRES_PASSWORD=postgresPwd -e POSTGRES_DB=football_players_registry -d -p 5532:5432 postgres
- 验证容器是否启动,运行
$ docker ps -a命令,查看创建的容器及其相对状态。 - 查看容器日志,运行
$ docker logs -f <容器 ID>命令,获取 PostgreSQL 状态信息。 - 连接到容器,运行
$ docker exec -it <容器 ID> bash命令。 - 登录 PostgreSQL,运行
$ psql -U postgres命令。 - 验证数据库列表,运行
$ \l命令。 - 连接到
football_players_registry数据库,运行$ \connect football_players_registry命令。 - 创建
FOOTBALL_PLAYER表,运行以下命令:
CREATE TABLE FOOTBALL_PLAYER(
ID SERIAL PRIMARY KEY NOT NULL,
NAME VARCHAR(50) NOT NULL,
SURNAME VARCHAR(50) NOT NULL,
AGE INT NOT NULL,
TEAM VARCHAR(50) NOT NULL,
POSITION VARCHAR(50) NOT NULL,
PRICE NUMERIC
);
- 检查表格结构,运行
$ \d+ football_player命令。
7. 创建源代码
使用 Spring Initializr 项目生成器工具( https://start.spring.io/ )获取项目骨架。实现微服务将使用以下组件:
- Web:包含使用 Tomcat Servlet 容器和 Spring MVC 进行完整 Web 开发所需的所有模块。
- Actuator:提供生产就绪功能,帮助监控和管理应用程序。
- DevTools:Spring Boot 开发工具。
- JPA:Java 持久化 API,包括 spring-data-jpa 、 spring-orm 和 Hibernate。
- PostgreSQL:PostgreSQL JDBC 驱动程序。
设置项目的 Maven Group 名称为 com.packtpub.springboot ,Artifact 为 football-player-microservice 。点击“Generate Project”创建并下载项目骨架的 ZIP 文件,解压到指定目录,使用喜欢的 IDE 打开 Maven 项目。
核心的 pom.xml 文件部分内容如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.packtpub.springboot</groupId>
<artifactId>football-player-microservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Boot Football player microservice</name>
<description>Demo project for Spring Boot</description>
在 src/main/resources 目录下的 application.properties 文件中设置数据库配置:
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5532/football_players_registry
spring.datasource.username= postgres
spring.datasource.password=postgresPwd
# This property always initialize the database using sql scripts set under resources directory
spring.datasource.initialization-mode=always
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.hibernate.ddl-auto=none
运行以下命令进行首次构建:
$ mvn clean package
构建完成后,运行项目:
$ java -jar target/football-player-microservice-0.0.1-SNAPSHOT.jar
使用执行器健康检查验证应用程序是否正确启动,访问 http://localhost:8080/actuator/health ,确保结果为 STATUS: "UP" 。
8. 实体类 - JPA
需要一个领域模型对象来映射插入数据库的记录,使用 JPA 规范创建实体类。
创建 com.packtpub.springboot.footballplayermicroservice.model 包,在其中创建 FootballPlayer 类:
import java.io.Serializable;
import java.math.BigInteger;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
/**
* Domain model class that maps the data stored into football_player table
* inside database.
*
* @author Mauro Vocale
* @version 1.0.0 29/09/2018
*/
@Entity
@Table(name = "football_player")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "FootballPlayer.findAll", query = "SELECT f FROM FootballPlayer f")
})
public class FootballPlayer implements Serializable {
private static final long serialVersionUID = -92346781936044228L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "name")
private String name;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "surname")
private String surname;
@Basic(optional = false)
@NotNull
@Column(name = "age")
private int age;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "team")
private String team;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 50)
@Column(name = "position")
private String position;
@Column(name = "price")
private BigInteger price;
public FootballPlayer() {
}
public FootballPlayer(String name, String surname, int age,
String team, String position, BigInteger price) {
this.name = name;
this.surname = surname;
this.age = age;
this.team = team;
this.position = position;
this.price = price;
}
}
9. 仓库 - JPA
Spring Data JPA 具有在运行时自动从仓库接口创建仓库实现的功能。
创建 com.packtpub.springboot.footballplayermicroservice.repository 包,在其中创建 FootballPlayerRepository 接口:
import org.springframework.data.repository.CrudRepository;
/**
* FootballPlayerRepository extends the CrudRepository interface.
* The type of entity and ID that it works with, FootballPlayer and Integer, are
* specified in the generic parameters on CrudRepository.
* By extending CrudRepository, FootballPlayerRepository inherits several
* methods for working with FootballPlayer persistence, including methods
* for saving, deleting, and finding FootballPlayer entities.
*
* @author Mauro Vocale
* @version 30/09/2018
*/
public interface FootballPlayerRepository extends CrudRepository<FootballPlayer, Integer> {
}
为了完成数据库访问操作管理,在 src/main/resources 目录下创建以下文件:
- schema.sql :包含表创建 SQL 命令的文件。
- data.sql :包含 SQL 插入数据命令的文件。
运行以下命令再次启动应用程序:
$ mvn spring-boot:run
可以看到嵌入式 Tomcat 执行了 SQL 创建模式和加载数据脚本。
10. RESTful 网络服务
Spring 框架提供了一种直观且易于使用的模型,通过 RESTful 网络服务暴露数据,这是微服务之间 API 通信的事实上的标准。2.0.5 版本会自动将 CRUD 操作暴露为 RESTful API。
FootballPlayerRepository 接口继承了 org.springframework.data.repository.CrudRepository 的以下方法:
public <S extends T> S save(S s);
public <S extends T> Iterable<S> saveAll(Iterable<S> itrbl);
public Optional<T> findById(ID id);
public boolean existsById(ID id);
public Iterable<T> findAll();
public Iterable<T> findAllById(Iterable<ID> itrbl);
public long count();
public void deleteById(ID id);
public void delete(T t);
public void deleteAll(Iterable<? extends T> itrbl);
public void deleteAll();
要自动暴露这些方法,需在 Maven pom.xml 文件中插入 spring-boot-starter-data-rest 依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
编译并运行项目:
$ mvn clean package && mvn spring-boot:run
打开浏览器访问 http://localhost:8080/ ,可以获取可用 API 的链接。访问 http://localhost:8080/footballPlayers 可以获取应用程序中存储的足球运动员列表。
也可以采用旧风格的方法,创建服务类和控制器类来调用 JPA 层并暴露 API。
创建 FootballPlayerService 类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.Iterable;
@Service
public class FootballPlayerService {
@Autowired
private FootballPlayerRepository repository;
public Iterable<FootballPlayer> findAll() {
return repository.findAll();
}
public FootballPlayer save(FootballPlayer entity) {
return repository.save(entity);
}
public void deleteById(Integer id) {
repository.deleteById(id);
}
public Optional<FootballPlayer> findById(Integer id) {
return repository.findById(id);
}
}
创建 FootballPlayerRESTController 类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
import java.util.Iterable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("/footballplayer")
public class FootballPlayerRESTController {
@Autowired
private FootballPlayerService service;
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public Iterable<FootballPlayer> findAll() {
return service.findAll();
}
@RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json")
public FootballPlayer save(@RequestBody FootballPlayer entity) {
return service.save(entity);
}
@RequestMapping(value = "/update/{id}", method = RequestMethod.PUT, produces = "application/json")
public FootballPlayer edit(@PathVariable Integer id, @RequestBody FootballPlayer entity) {
return service.save(entity);
}
@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE, produces = "application/json")
public void delete(@PathVariable Integer id) {
service.deleteById(id);
}
@RequestMapping(value = "/show/{id}", method = RequestMethod.GET, produces = "application/json")
public Optional<FootballPlayer> findById(@PathVariable Integer id) {
return service.findById(id);
}
}
调用 API 获取足球运动员列表:
$ curl http://localhost:8080/footballplayer | json_pp
11. JUnit 测试
为确保 API 正常工作,添加以下 Maven 依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
在 com.packtpub.springboot.footballplayermicroservice.FootballPlayerMicroserviceApplicationTests 类中编写测试方法:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FootballPlayerMicroserviceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FootballPlayerMicroserviceApplicationTests {
private final HttpHeaders headers = new HttpHeaders();
private final TestRestTemplate restTemplate = new TestRestTemplate();
@Autowired
private FootballPlayerRepository repository;
@org.springframework.boot.web.server.LocalServerPort
private int port;
@Test
public void test_1_FindAll() throws IOException {
System.out.println("findAll");
HttpEntity<String> entity = new HttpEntity<>(null, headers);
ResponseEntity<String> response =
restTemplate.exchange(createURLWithPort("/footballplayer"),
HttpMethod.GET, entity, String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
JSONArray jsonArray = JsonPath.read(response.getBody(), "$.[*]");
assertEquals(23, jsonArray.size());
}
@Test
public void test_2_Create() {
System.out.println("create");
FootballPlayer player = new FootballPlayer("Mauro", "Vocale", 38,
"Juventus", "central midfielder", new BigInteger("100"));
HttpEntity<FootballPlayer> entity = new HttpEntity<>(player, headers);
ResponseEntity<String> response = restTemplate.exchange(
createURLWithPort("/footballplayer/save"),
HttpMethod.POST, entity, String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("{\"id\":24,\"name\":\"Mauro\",\"surname\":\"Vocale\",\"age\":38,\"team\":\"Juventus\",\"position\":\"central midfielder\",\"price\":100}", response.getBody());
}
}
以下是整个流程的 mermaid 流程图:
graph LR
A[开始] --> B[选择构建工具]
B --> C{Maven 或 Gradle}
C -- Maven --> D[配置 pom.xml]
C -- Gradle --> E[配置 build.gradle]
D --> F[构建项目 mvn clean package]
E --> F
F --> G[安装和配置 PostgreSQL]
G --> H[创建实体类和仓库接口]
H --> I[创建数据库脚本 schema.sql 和 data.sql]
I --> J[运行项目 mvn spring-boot:run]
J --> K[添加 RESTful 服务依赖]
K --> L[编译运行项目 mvn clean package && mvn spring-boot:run]
L --> M[测试 API]
M --> N[结束]
使用 Spring Boot 2 构建微服务
12. 测试类分析
在 FootballPlayerMicroserviceApplicationTests 测试类中,主要进行了两个关键测试:
- test_1_FindAll 方法 :
- 该方法的主要目的是测试获取所有足球运动员信息的 API 是否正常工作。
- 首先,创建了一个 HttpEntity 对象,用于封装请求的头部信息。
- 然后,使用 TestRestTemplate 发送一个 GET 请求到 /footballplayer 接口。
- 通过 assertThat 断言,验证响应的状态码是否为 HttpStatus.OK 。
- 接着,使用 JsonPath 解析响应体,将其转换为 JSONArray ,并验证数组的大小是否符合预期。
- test_2_Create 方法 :
- 此方法用于测试创建足球运动员信息的 API。
- 先创建一个 FootballPlayer 对象,包含球员的详细信息。
- 同样创建一个 HttpEntity 对象,将球员信息和头部信息封装其中。
- 使用 TestRestTemplate 发送一个 POST 请求到 /footballplayer/save 接口。
- 通过 assertThat 断言,验证响应的状态码是否为 HttpStatus.OK ,以及响应体是否包含正确的球员信息。
13. 总结与注意事项
- 依赖管理 :使用
spring-boot-starter-parent可以方便地管理 Spring Boot 项目的依赖版本,避免版本冲突。 - 数据库配置 :在
application.properties中正确配置数据库连接信息,确保应用程序能够正常连接到 PostgreSQL 数据库。 - RESTful API 暴露 :通过
spring-boot-starter-data-rest可以自动将CrudRepository中的方法暴露为 RESTful API,但在实际开发中,也可以根据需求手动创建服务类和控制器类。 - 测试 :编写 JUnit 测试用例可以确保 API 的正确性和稳定性,在测试时要注意模拟真实的请求和响应。
14. 常见问题及解决方法
| 问题描述 | 可能原因 | 解决方法 |
|---|---|---|
| 应用程序无法启动 | 数据库连接失败、依赖冲突等 | 检查 application.properties 中的数据库配置,确保数据库服务正常运行;检查 pom.xml 或 build.gradle 中的依赖版本是否兼容。 |
| RESTful API 无法访问 | 配置错误、端口被占用等 | 检查 pom.xml 中是否添加了 spring-boot-starter-data-rest 依赖;检查端口是否被其他应用程序占用。 |
| 测试用例失败 | 数据不一致、API 实现错误等 | 检查数据库中的数据是否符合测试预期;检查服务类和控制器类的实现是否正确。 |
15. 扩展功能建议
- 添加更多的业务逻辑 :在
FootballPlayerService类中添加更多的业务逻辑,例如数据验证、权限控制等。 - 集成其他服务 :可以将该微服务与其他服务集成,例如缓存服务、消息队列服务等,以提高系统的性能和可靠性。
- 使用 Swagger 进行 API 文档管理 :虽然由于 SpringFox 框架的 bug 暂时无法使用 Swagger 进行 API 文档管理,但可以关注该框架的更新,待 bug 修复后使用 Swagger 自动生成 API 文档。
16. 后续开发流程
以下是后续开发的 mermaid 流程图:
graph LR
A[现有项目] --> B[添加业务逻辑]
B --> C[集成其他服务]
C --> D[使用 Swagger 管理 API 文档]
D --> E[持续测试和优化]
E --> F[部署到生产环境]
F --> G[监控和维护]
在后续开发中,可以按照上述流程逐步完善和优化微服务。先在现有项目基础上添加更多的业务逻辑,增强系统的功能。然后集成其他服务,提升系统的性能和可靠性。接着使用 Swagger 进行 API 文档管理,方便开发人员和测试人员使用。在开发过程中持续进行测试和优化,确保系统的稳定性和正确性。最后将系统部署到生产环境,并进行监控和维护,及时处理出现的问题。
通过以上步骤,我们可以构建一个完整、稳定且功能强大的 Spring Boot 微服务,满足实际业务的需求。
超级会员免费看
71

被折叠的 条评论
为什么被折叠?



