使用微服务的Spring
据笔者所知,从未接触过Spring Framework 的Java开发人员可谓寥寥无几。实际上,Spring Framework是由许多项目组成的,它可以与许多其他框架一起使用,开发人员迟早都将被迫尝试使用它。虽然Spring Boot 的应用经验相当不常见,但它很快就获得了很高的人气。与Spring Framework相比,Spring Boot是一个相对较新的解决方案。它的实际版本是2,而不是Spring Framework的5。那么,创建它的目的是什么?使用Spring Boot与使用标准 Spring Framework方式运行应用程序究竟有什么区别呢?
本章将要讨论的主题包括:
口使用启动器以启用项目的其他功能。
口使用Spring Web库实现公开 REST API方法的服务。
口使用属性和YAML文件自定义服务配置。
口详细说明并提供公开的REST端点的规范。配置运行状况检查和监控功能。
口使用Spring Boot配置文件以使应用程序适应不同模式运行。
口使用ORM功能与嵌入式和远程NoSQL 数据库进行交互。
关于Spring Boot
Spring Boot专门用于运行独立的Spring应用程序。它与简单的Java应用程序一样,使用java -jar命令。使 Spring Boot与标准Spring 配置不同的基本原因是简单性( Simplicity)。这种简单性与我们需要了解的第一个重要术语密切相关,即启动器( Starter)。启动器是一个可以包含在项目依赖项中的工件(Artifact)。它只是为必须包含在应用程序中的其他工件提供一组依赖项(Dependency),以实现所需的功能。以这种方式提供的包可以使用,这意味着开发人员不必配置任何东西即可使其工作。这带来了与Spring Boot相关的第二个重要术语:自动配置(Auto-Configuration)。启动器包含的所有工件都具有默认设置,可以使用属性或其他类型的启动器轻松覆盖。例如,如果开发人员在某个应用程序的依赖项中包含spring-boot-starter-web,则它会嵌入一个默认Web容器,并在应用程序启动期间在默认端口上启动它。继续深入了解可知,Spring Boot中的默认Web容器是Tomcat,它从端口8080开始。开发人员可以通过声明应用程序属性文件中的指定字段来轻松更改此端口,甚至还可以通过在项目依赖项中包含 spring-boot-starter-jetty 或
spring-boot-starter-undertow来更改Web容器。
关于Starter,也就是启动器,这里还是有必要再多介绍一些。它们的官方命名模式是spring-boot-starter-*,其中,*就是指特定类型的启动器。Spring Boot中有很多可用的启动器,本节将仅选取一些最受欢迎的启动器进行简要介绍(见表2.1),这些启动器也将应用于本书后面章节所提供的示例中。
如果开发人员对可用启动器的完整列表感兴趣,请参阅Spring Boot 规范。现在,不妨回过头来讨论Spring Boot 与 Spring Framework标准配置之间的主要区别。正如前文所述,开发人员可以包含spring-boot-starter-web,它会将Web容器嵌入应用程序中。如果使用的是标准的Spring配置,则开发人员不会将Web容器嵌入应用程序中,而是将其作为WAR文件部署到Web容器上。这是一个关键的区别,也是Spring Boot用于创建部署在微服务架构中的应用程序的最重要原因之一。微服务的一个主要特征是独立于其他微服务。在这种情况下,很明显它们不应共享公共资源(如数据库或Web容器)。由此可见,在一个Web容器上部署许多WAR文件是微服务所不能接受的模式。因此,Spring Boot是显而易见的选择。
就个人而言,笔者在开发许多应用程序时都使用了Spring Boot,而不仅仅是在微服务环境中工作时。如果开发人员尝试使用了Spring Boot而不是标准的Spring Framework配置,那么笔者相信他将再也不想回过头来使用标准的Spring Framework配置。为了支持这个结论,我们找到了一个有趣的图表,它说明了Java框架存储库在GitHub上的流行程度,其网址为
https://redmonk.com/fryan/2017/06/22/language-framework-popularity-a-look-at-java-june-2017/。开发人员也可以通过网络搜索找到最新的此类统计图表。
服务。在这种情况下,很明显它们不应共享公共资源(如数据库或Web容器)。由此可见,在一个Web容器上部署许多WAR文件是微服务所不能接受的模式。因此,Spring Boot是显而易见的选择。
就个人而言,笔者在开发许多应用程序时都使用了Spring Boot,而不仅仅是在微服务环境中工作时。如果开发人员尝试使用了Spring Boot而不是标准的Spring Framework配置,那么笔者相信他将再也不想回过头来使用标准的Spring Framework配置。为了支持这个结论,我们找到了一个有趣的图表,它说明了Java框架存储库在GitHub上的流行程度,其网址为
https://redmonk.com/fryan/2017/06/22/1language-framework-popularity-a-look-at-java-june-2017/。开发人员也可以通过网络搜索找到最新的此类统计图表。
接下来我们将具体讨论如何使用Spring Boot开发应用程序。
使用Spring Boot开发应用程序
要在项目中启用 Spring Boot,我们推荐的方法是使用依赖关系管理系统(DependencyManagement System)。在这里,开发人员可以看到如何在Maven和 Gradle项目中包含适当工件的简短片段。以下是Maven项目pom. xml中的示例片段。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId><version>1.5.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>/dependency>
</ dependencies>
使用Gradle,开发人员将不需要定义父依赖项。以下是 build.gradle中的一个片段。
plugins{
id 'org.springframework.boot' version '1.5.7.RELEASE'
)
dependenciesi
compile( "org.springframework.boot:spring-boot-starter-web: 1.5.7.RELEASE")
在使用Maven时,没有必要继承
spring-boot-starter-parent POM。或者,开发人员也可以使用以下依赖关系管理机制。
<dependencyManagement>
<dependencies>
<dependeney>
<groupId>org.springframework .boot</groupId>
<artifactId>spring-boot-dependencies</artifactId><version>1.5.7.RELEASE</version>
<type>pom</type>
<scope>import</scope></dependeney>
</dependencies>
</dependencyManagement>
现在,开发人员所需要的只是创建主应用程序类并使用@SpringBootApplication来注解它,这相当于联合使用其他3个注解(Annotation),即@Configuration 、@EnableAutoConfiguration和@ComponentScan:
eSpringBootApplication
public class Application{
public static void main(String[]args){
SpringApplication.run(Application.class, args);
)
一旦声明了主类和 spring-boot-starter-web,则开发人员只需要运行第一个应用程序。而且,如果开发人员使用了诸如 Eclipse或IntelliJ之类的集成开发环境( IntegratedDevelopment Environment,IDE),则应该只运行主类。否则,必须像标准Java应用程序一样使用java -jar命令构建和运行应用程序。首先,开发人员应该在应用程序构建期间提供负责将所有依赖项打包到可执行JAR的配置。这个可执行JAR有时也被称为胖JAR(Fat JAR)。如果在 Maven项目的pom.xml 中定义了spring-boot-maven-plugin,则会执行该操作。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin></plugins></ build>
该示例应用程序在Tomcat 容器上仅执行启动Spring环境,Tomcat容器在端口8080上可用。胖JAR的大小约为14MB。开发人员可以使用集成开发环境轻松查看项目中包含的库。这些都是基本的Spring库,如 spring-core、spring-aop、spring-context,Spring Boot,已经嵌入的Tomcat,用于日志记录的库如 Logback、Log4j和Slf4j,此外还有用于JSON的序列化(Serialization)或反序列化(Deserialization)的Jackson库。为项目设置默认的Java版本是一个很好的选择。开发人员可以通过声明java.version属性在pom.xml中轻松设置它。
<properties>
<java. version>1.8</java.version>
</properties>
开发人员可以通过向Jetty服务器添加新的依赖项来更改默认的Web容器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</ dependency
自定义配置文件
快速创建应用程序并且无须很大的工作量,这两者对于开发人员来说其实是一回事,但同样重要的是能够轻松自定义和覆盖默认设置。在这一方面,Spring Boot可以派上用场,提供支持配置管理的机制。要自定义配置,最简单的方法是使用配置文件,然后将配置文件追加到应用程序胖JAR。Spring Boot 会自动检测名称以application前缀开头的配置文件。它所支持的文件类型是.properties 和.yml。
因此,开发人员可以创建诸如 application.properties或 application.yml 之类的配置文件,甚至包括特定于配置文件的文件,如
application-prod.properties或 application-dev.yml。此外,开发人员还可以使用操作系统环境变量和命令行参数来外部化配置。使用properties或YAML文件时,它们应放在以下位置之一。
口当前应用程序目录的/config子目录。
口当前应用程序目录。
口类路径/config包(例如,在JAR中)。
口类路径根。
如果要为配置文件指定一个特定名称,而不是 application或 application - {profile} ,则需要在启动期间提供spring. config.name环境属性。开发人员还可以使用spring.config.location属性,该属性包含以逗号分隔的目录位置或文件路径列表。
java -jar sample-spring-boot-web.jar - - spring .config. name=example
java -jar sample-spring-boot-web.jar--
spring。config. location=classpath:/example.properties
在配置文件内部,开发人员可以定义两种类型的属性。首先,有一组常见的预定义Spring Boot 属性,它主要由来自sringobo-autoconfigure 库的底层类使用。其次,开发人员还可以考虑定义自己的自定义配置属性,然后使用@ConfigurationProperties或@Value注解将其注入应用程序。
现在可以先从预定义的属性开始。Spring Boot 项目所支持的完整列表可以在它们的说明文档的Common application properties (常见应用程序属性)一节的 Appendix A (附录A)中得到。其中,大多数都特定于某些Spring模块,如数据库、Web 服务器、安全性模块和其他- -些解决方案等,但是也有一组核心属性。 就个人而言,笔者更喜欢使用YAML而不是properties文件,因为YAML文件更方便阅读,当然,具体使用哪一种文件取决于开发人员的个人喜好。最常见的是覆盖应用程序名称之类的属性(该名称将用于服务发现和分布式配置管理)、网络服务器端口、日志或数据库连接设置。
一般来说, application.yml 文件存放在src/main/resources目录中,然后在Maven构建之后位于JAR根目录中。以下是一个配置文件示例,它将覆盖默认服务器端口、应用程序名称和日志记录属性。
server:
port: S{port:2222}
spring:
application:
name: first-service
logging:
pattern:
console: "d{HH:mm:ss.SSs} 号-51eve1- %logger{36} -号msg号n"
file: "&d{HH:mm:ss.SSS) [thread] 8-51evel 8logger{36] - 8msg8n"
level:
org . spr ingframework.web: DEBUG
file: app.1og
这里很值得称道的是,开发人员不必为日志记录配置定义任何其他外部配置文件,如log4j.xml 或logback.xml。 在之前的文件片段中,可以看到我们已经将org,springframework.web的默认日志级别更改为DEBUG和日志模式,并创建了一个日志文件app:log,放置在当前应用程序目录中。现在,默认的应用程序名称是first-service, 默认HTTP端口是2222.
开发人员的自定义配置设置也应放在相同的properties 或YAML文件中。以下是一个带有自定义属性的application.yml示例。
name: first-service
my:
servers:
一dev.bar .com
一foo.bar.com
也可以使用@Value注解注入一个简单的属性。
@Component
public class CustomBean
@Value ("$ (name)")
private String name;
还可以使用@ConfigurationProperies注解注入更复杂的配置属性。例如,在YAML文件中的my servers属性中定义的值列表即可被注入java.util.List类型的目标bean中。
@ConfigurationProperties (prefix- "my")
public class Config {
private List<string> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
到目前为止,我们已经设法创建了-个简单的应用程序,这个程序除了在Tomcat或Jetty等Web容器上启动Spring之外什么都没做。本章的这一部分 旨在向开发人员展示使用Spring Boot启动应用程序开发是多么简单。除此之外,我们还介绍了如何使用YAML。
或properties文件自定义配置。对于那些喜欢点击而不是打字的人,我们推荐使用SpringInitializr网站(tps://art.spring.io/. ,在该网站可以根据自己选择的选项生成项目存根(Project Stub)。在简单的站点视图中,开发人员可以选择构建工具(Maven/Gradle) 、编程语言(Java/Kotlin/Groovy) 和Spring Boot 版本等,然后开发人员应该在Search fordependencies (搜索依赖项)标签后提供使用该搜索引擎的所有必需的依赖项。我们已经选择了包括springboot-starter-web,它在Spring Initializr上标记为Web,如图2.1所示即为该网站的屏幕截图。单击Generate Project (生成项目)后,包含生成的源代码的ZIP文件将下载到开发人员的计算机上。此外,感兴趣的开发人员也可以单击Swich to the fullversion (切换到完整版本),这样就可以看到几乎所有可用的Spring Boot和Spring Cloud库,它们都可以包含在生成的项目中。
既然已经讨论了使用Spring Boot构建项目的基础知识,那么现在正是为示例应用程序添加--些新功能的最佳时机。
创建RESTful Web服务
作为第一步,现在不妨来创建RESTful Web服务,向调用客户端公开一些数据。如前所述,负责JSON消息序列化和反序列化的Jackson库将与pionostarter-web一起自动包含在类路径中。鉴于此,开发人员不需要做任何事情,只需声明一个模型类,然后由该模型类通过REST方法返回或用作参数。
以下是示例模型类Person。
public class Person {
private Long id;
private String firstName ;
private String lastName;
private int age;
private Gender gender;
public Long getId() {
return id;
public void setId(Long id) (
this.id = id;
Spring Web提供了一些可用于创建RESTful Web服务的注解。其中的第-一个注解是Restontoller该注解应该在负责处理传入HTTP请求的控制器bean类上设置。此外还有@RequestMapping注解,它通常用于将控制器方法映射到HTTP。正如下面的文件片段所示,它可以在整个控制器类上使用,以设置其中所有方法的请求路径。开发人员可以为具体的HTTP方法使用更具体的注解,如@GetMapping 或@PostMapping。 这里值得注意的是,@GetMapping与@RequestMapping相同,它们都具有参数method = RequestMethod.GET.另外两个常用的注解是@RequestParam和@RequestBody. @RequestParam 可以绑定路径并查询对象的参数:而@RequestBody 则可以使用Jackson库将JSON映射到对象。
@RestController
@RequestMapping ("/person" )
public class PersonController {
private List<Person> persons = new ArrayList<>();
@GetMapping
public List<Person> findA1ll ()- {
return persons;
}
@GetMapping("/{id}")
public Person findById (@RequestParam("id") Long id) {
return persons.stream() .filter(it ->
it.getId() .equals (id)) .findFirst().get();
OPos tMapping
public Person add (eRequestBody Person p){
p.setId((long) (persons.size()+1));
persons.add(p) ;
return P;
为了与REST API标准兼容,开发人员应该处理PUT和DELETE方法。在其实现之
后,开发人员将执行所有创建(Create)、检索(Retrieve)、更新(Update)和删除(Delete )
操作,如表2.2所示。
以下是使用DELETE和PUT方法的@RestController实现示例的代码片段。
@DeleteMapping("/{id}")
public void delete (@RequestParam("id") Long id) {
List<Person> P = persons.stream() . filter(it ->
it.getId() .equals(id)) .collect (Collectors.toList();
persons.removeAll(p);
@PutMapping
public void update (@RequestBody Person p) (
Person person = persons.stream() . filter(it >
it.getId() .equals(p.getId0)) . findFirst() .get();
persons .set (persons. indexof (person), p);
控制器的代码非常简单。它将所有数据都存储在本地java.util.List中,这显然不是一个好的编程习惯。当然,这只是为了示例需要而采取的简化的权宜设置。在本章第2.6节“将应用程序与数据库集成”一节中,还将详细介绍与NoSQL数据库集成的更高级的示例应用程序。可能部分开发人员有SOAP Web服务的开发经验。如果已经使用SOAP而不是REST创建了类似的服务,则开发人员将为客户端提供-一个WSDL文件,其中描述了所有服务定义。糟糕的是,REST 并不支持像WSDL这样的标准表示法(Notation) 。在RESTful Web服务的初始阶段,有人说Web应用程序描述语言(Web Application Description Language,WADL)将执行该角色。但实际情况是,许多提供商(包括Spring Web)在应用程序启动后都不会生成WADL文件。
当然,这只是一个题外话。简而言之,我们已经完成了第一个微服务,它通过HTTP公开了一些REST操作。开发人员可能在构建胖JAR之后从集成开发环境或使用java jar命令来运行此微服务。如果开发人员没有更改application.yml文件中的配置属性,或者在运行应用程序时未设置-Dport选项,则可以在ht:t/oca/hot/2222下找到它。为了让其他开发人员能够调用我们的API,这里有两种选择:一种 方法是共享描述其用法的文档:另一种方法是采用自动API客户端生成的机制,或者也可以两种方法并行,而这正是Swagger的用武之地。