Spring-和-SpringBoot-微服务指南-全-

Spring 和 SpringBoot 微服务指南(全)

原文:zh.annas-archive.org/md5/508bcfa60cfda269c2649ebff6cca176

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

现如今,开发者面临着竞争压力,这影响了他们构建应用程序的方式,包括更快的交付、可扩展性和高性能。微服务有助于将应用程序分解成小的服务,并远离单一的大型工件。在这种情况下,我们可以构建可扩展、灵活且高弹性的系统。Spring Boot 有助于构建这样的面向 REST 的生产级微服务。

因此,如果您想使用 Spring Boot 构建微服务,您就走在正确的道路上。

我能从中得到什么?

地图对于您的旅程至关重要,尤其是在您在其他大陆度假时。当涉及到学习时,路线图可以帮助您为达到目标提供明确的路径。因此,在您开始旅程之前,您将看到一条路线图。

本书精心设计和开发,旨在为您提供有关 Spring Boot 的所有正确和相关信息。我们为您创建了这个学习路径,它包括三个课程:

课程 1,使用 Spring Boot 构建微服务,涵盖了 Spring Boot 和 REST 服务的基础知识。您将探索 Spring Boot 的不同特性,并创建一些具有良好测试的 REST 服务。

课程 2,扩展微服务,涵盖了如何向应用程序添加异常处理、缓存和国际化的功能。您将学习使用 Swagger 记录 REST 服务的最佳实践。您还将了解如何使用 Spring 安全保护微服务的基础知识。

课程 3,高级 Spring Boot 特性,探讨了 Spring Boot 中的高级特性。您将学习如何使用 Spring Boot Actuator 监控微服务。然后,您将学习如何将微服务部署到云中。您还将学习如何利用 Spring Boot 提供的开发者工具更有效地开发。

我能从本书中获得什么?

  • 使用 Spring Initializr 创建基本的 Spring 项目

  • 使用 Spring Boot 构建基本的微服务

  • 实现缓存和异常处理

  • 使用 Spring 安全性和 OAuth2 保护您的微服务

  • 使用自包含的 HTTP 服务器部署微服务

  • 使用 Spring Boot Actuator 监控您的微服务

  • 学习如何使用开发者工具更有效地开发

先决条件

本书面向了解 Spring 编程基础并希望使用 Spring Boot 构建微服务的 Java 开发者。在您开始本书之前,需要满足以下先决条件:

  • 对 Java 有实际操作知识

  • 对 Spring 编程有基本了解

第一章:使用 Spring Boot 构建 Microservices

正如我们在上一课中讨论的,我们正在向具有更小、独立可部署的微服务的架构迈进。这意味着将会有大量的较小微服务被开发。

一个重要的后果是,我们需要能够快速启动并运行新的组件。

Spring Boot 旨在解决快速启动新组件的问题。在本课中,我们将从了解 Spring Boot 带来的功能开始。我们将回答以下问题:

  • 为什么选择 Spring Boot?

  • Spring Boot 提供了哪些功能?

  • 什么是自动配置?

  • Spring Boot 不是什么?

  • 当你使用 Spring Boot 时,后台会发生什么?

  • 你如何使用 Spring Initializr 创建新的 Spring Boot 项目?

  • 你如何使用 Spring Boot 创建基本的 RESTful 服务?

什么是 Spring Boot?

首先,让我们先澄清一些关于 Spring Boot 的误解:

  • Spring Boot 不是一个代码生成框架。它不会生成任何代码。

  • Spring Boot 既不是应用服务器,也不是网络服务器。它与不同范围的应用程序和网络服务器提供了良好的集成。

  • Spring Boot 不实现任何特定的框架或规范。

这些问题仍然存在:

  • 什么是 Spring Boot?

  • 为什么它在过去几年变得如此流行?

为了回答这些问题,让我们构建一个快速示例。让我们考虑一个你想要快速原型化的示例应用程序。

为微服务构建快速原型

假设我们想要使用 Spring MVC 和 JPA(以 Hibernate 作为实现)来连接数据库,构建一个微服务。

让我们考虑设置此类应用程序的步骤:

  1. 决定要使用哪些版本的 Spring MVC、JPA 和 Hibernate。

  2. 设置 Spring 上下文以将所有不同的层连接在一起。

  3. 使用 Spring MVC(包括 Spring MVC 配置)设置一个网络层:

    • 为 DispatcherServlet、处理器、解析器、视图解析器等配置 Bean
  4. 在数据层设置 Hibernate:

    • 为 SessionFactory、数据源等配置 Bean
  5. 决定并实现如何存储你的应用程序配置,这些配置在不同环境中会有所不同。

  6. 决定你将如何进行单元测试。

  7. 决定并实现你的事务管理策略。

  8. 决定并实现如何实现安全性。

  9. 设置你的日志框架。

  10. 决定并实现你希望在生产中监控应用程序的方式。

  11. 决定并实现一个指标管理系统,以提供有关应用程序的统计数据。

  12. 决定并实现如何将你的应用程序部署到网络或应用服务器。

至少有一些提到的步骤必须在我们可以开始构建我们的业务逻辑之前完成。这至少需要几周的时间。

当我们构建微服务时,我们希望快速开始。所有前面的步骤都不会使开发微服务变得容易。这正是 Spring Boot 旨在解决的问题。

以下引用摘自 Spring Boot 网站(docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-documentation):

Spring Boot 使得创建独立、生产级别的基于 Spring 的应用程序变得容易,你可以“直接运行”。我们对 Spring 平台和第三方库持有一个有见地的观点,这样你可以以最小的麻烦开始。大多数 Spring Boot 应用程序需要非常少的 Spring 配置

Spring Boot 使开发者能够专注于微服务背后的业务逻辑。它旨在处理开发微服务所涉及的所有繁琐的技术细节。

主要目标

Spring Boot 的主要目标如下:

  • 使基于 Spring 的项目能够快速启动。

  • 有见地。基于常见用法做出默认假设。提供配置选项以处理与默认值的偏差。

  • 提供一系列开箱即用的非功能性特性。

  • 不使用代码生成,并避免使用大量的 XML 配置。

非功能性特性

Spring Boot 提供的部分非功能性特性如下:

  • 默认处理各种框架、服务器和规范的版本和配置

  • 应用程序安全性的默认选项

  • 默认应用度量并提供扩展的可能性

  • 使用健康检查进行基本的应用程序监控

  • 多种外部化配置选项

Spring Boot Hello World

我们将在本课中开始构建我们的第一个 Spring Boot 应用程序。我们将使用 Maven 来管理依赖。

启动 Spring Boot 应用程序涉及以下步骤:

  1. 在你的pom.xml文件中配置spring-boot-starter-parent

  2. 配置pom.xml文件以包含所需的启动项目。

  3. 配置spring-boot-maven-plugin以能够运行应用程序。

  4. 创建你的第一个 Spring Boot 启动类。

让我们从第一步,配置启动项目开始。

配置 spring-boot-starter-parent

让我们从包含spring-boot-starter-parent的简单pom.xml文件开始:

    <project 

     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.mastering.spring</groupId> 
    <artifactId>springboot-example</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <name>First Spring Boot Example</name> 
    <packaging>war</packaging>
    <parent> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-parent</artifactId>  
      <version>2.0.0.M1</version>
    </parent>
    <properties> 
      <java.version>1.8</java.version> 
    </properties>

   <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
   </repositories>

   <pluginRepositories>
    <pluginRepository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
        <snapshots>
          <enabled>false</enabled>
        </snapshots>
     </pluginRepository>
    </pluginRepositories>

</project>

第一个问题是这样的:为什么我们需要spring-boot-starter-parent

spring-boot-starter-parent依赖包含要使用的 Java 默认版本、Spring Boot 使用的依赖默认版本以及 Maven 插件的默认配置。

注意

spring-boot-starter-parent依赖是提供基于 Spring Boot 应用程序的依赖和插件管理的父 POM。

让我们看看spring-boot-starter-parent内部的代码,以更深入地了解spring-boot-starter-parent

spring-boot-starter-parent

spring-boot-starter-parent 依赖项继承自 spring-boot-dependencies,它在 POM 的顶部定义。以下代码片段展示了 spring-boot-starter-parent 的一个摘录:

    <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.0.0.M1</version>
      <relativePath>../../spring-boot-dependencies</relativePath>
   </parent>

spring-boot-dependencies 为 Spring Boot 使用的所有依赖项提供默认的依赖管理。以下代码展示了在 spring-boot-dependencies 中配置的各种依赖项的不同版本:

<activemq.version>5.13.4</activemq.version>
<aspectj.version>1.8.9</aspectj.version>
<ehcache.version>2.10.2.2.21</ehcache.version>
<elasticsearch.version>2.3.4</elasticsearch.version>
<gson.version>2.7</gson.version>
<h2.version>1.4.192</h2.version>
<hazelcast.version>3.6.4</hazelcast.version>
<hibernate.version>5.0.9.Final</hibernate.version>
<hibernate-validator.version>5.2.4.Final</hibernate
  validator.version>
<hsqldb.version>2.3.3</hsqldb.version>
<htmlunit.version>2.21</htmlunit.version>
<jackson.version>2.8.1</jackson.version>
<jersey.version>2.23.1</jersey.version>
<jetty.version>9.3.11.v20160721</jetty.version>
<junit.version>4.12</junit.version>
<mockito.version>1.10.19</mockito.version>
<selenium.version>2.53.1</selenium.version>
<servlet-api.version>3.1.0</servlet-api.version>
<spring.version>4.3.2.RELEASE</spring.version>
<spring-amqp.version>1.6.1.RELEASE</spring-amqp.version>
<spring-batch.version>3.0.7.RELEASE</spring-batch.version>
<spring-data-releasetrain.version>Hopper-SR2</spring-
  data-releasetrain.version>
<spring-hateoas.version>0.20.0.RELEASE</spring-hateoas.version>
<spring-restdocs.version>1.1.1.RELEASE</spring-restdocs.version>
<spring-security.version>4.1.1.RELEASE</spring-security.version>
<spring-session.version>1.2.1.RELEASE</spring-session.version>
<spring-ws.version>2.3.0.RELEASE</spring-ws.version>
<thymeleaf.version>2.1.5.RELEASE</thymeleaf.version>
<tomcat.version>8.5.4</tomcat.version>
<xml-apis.version>1.4.01</xml-apis.version>

如果我们想覆盖特定版本的依赖项,我们可以在应用程序的 pom.xml 文件中提供一个具有正确名称的属性来实现。以下代码片段展示了如何配置我们的应用程序使用 Mockito 的 1.10.20 版本:

    <properties>
     <mockito.version>1.10.20</mockito.version>
    </properties>

以下是在 spring-boot-starter-parent 中定义的一些其他内容:

  • 默认的 Java 版本 <java.version>1.8</java.version>

  • Maven 插件的默认配置:

    • maven-failsafe-plugin

    • maven-surefire-plugin

    • git-commit-id-plugin

不同框架版本之间的兼容性是开发者面临的主要问题之一。我如何找到与特定版本 Spring 兼容的最新 Spring Session 版本?通常的答案会是阅读文档。然而,如果我们使用 Spring Boot,这通过 spring-boot-starter-parent 变得简单。如果我们想升级到较新的 Spring 版本,我们只需要找到那个 Spring 版本的 spring-boot-starter-parent 依赖项。一旦我们将应用程序升级到使用那个特定的 spring-boot-starter-parent 版本,我们就会拥有所有其他依赖项升级到与新 Spring 版本兼容的版本。这减少了开发者需要处理的问题。总是让我感到高兴。

使用必需的入门项目配置 pom.xml

每当我们想在 Spring Boot 中构建应用程序时,我们都需要开始寻找入门项目。让我们专注于理解入门项目是什么。

理解入门项目

入门项目是针对不同目的简化的依赖描述符。例如,spring-boot-starter-web 是用于构建 Web 应用程序(包括 RESTful)的入门项目,使用 Spring MVC。它使用 Tomcat 作为默认的嵌入式容器。如果我想使用 Spring MVC 开发 Web 应用程序,我们只需要在我们的依赖项中包含 spring-boot-starter-web,我们就会自动获得以下预配置:

  • Spring MVC

  • jackson-databind(用于绑定)和 hibernate-validator(用于表单验证)的兼容版本

  • spring-boot-starter-tomcat(Tomcat 的入门项目)

以下代码片段展示了在 spring-boot-starter-web 中配置的一些依赖项:

    <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-validator</artifactId>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
       </dependency>
    </dependencies>

正如前一个摘录中所示,当我们使用 spring-boot-starter-web 时,我们会自动配置许多框架。

对于我们想要构建的 Web 应用程序,我们还想做一些良好的单元测试,并在 Tomcat 上部署它。以下片段显示了我们需要的不同启动依赖项。我们需要将其添加到我们的 pom.xml 文件中:

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-tomcat</artifactId>
       <scope>provided</scope>
     </dependency>
    </dependencies>

我们添加了三个启动项目:

  • 我们已经讨论了 spring-boot-starter-web。它为我们提供了构建带有 Spring MVC 的 Web 应用程序所需的框架。

  • spring-boot-starter-test 依赖项提供了以下单元测试所需的测试框架:

    • JUnit:基本的单元测试框架

    • Mockito:用于模拟

    • HamcrestAssertJ:用于可读断言

    • Spring Test:基于 spring-context 的应用程序的单元测试框架

  • spring-boot-starter-tomcat 依赖项是运行 Web 应用程序时的默认选项。我们包括它以增加清晰度。spring-boot-starter-tomcat 是使用 Tomcat 作为嵌入式 Servlet 容器的启动器。

我们现在已经配置了 pom.xml 文件,包括启动父项目和所需的启动项目。现在让我们添加 spring-boot-maven-plugin,这将使我们能够运行 Spring Boot 应用程序。

配置 spring-boot-maven-plugin

当我们使用 Spring Boot 构建应用程序时,可能存在几种情况:

  • 我们希望在不构建 JAR 或 WAR 文件的情况下运行应用程序

  • 我们希望构建 JAR 和 WAR 文件以供后续部署

spring-boot-maven-plugin 依赖项为上述两种情况都提供了功能。以下片段显示了如何在应用程序中配置 spring-boot-maven-plugin

    <build>
     <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
     </plugins>
    </build>

spring-boot-maven-plugin 依赖项为 Spring Boot 应用程序提供了几个目标。最受欢迎的目标是运行(这可以在项目根目录的命令提示符下作为 mvn spring-boot:run 执行)。

创建您的第一个 Spring Boot 启动类

以下类解释了如何创建一个简单的 Spring Boot 启动类。它使用 SpringApplication 类中的静态 run 方法,如下面的代码片段所示:

    package com.mastering.spring.springboot;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot
    autoconfigure.SpringBootApplication;
    import org.springframework.context.ApplicationContext;
    @SpringBootApplication public class Application {
       public static void main(String[] args)
        { 
         ApplicationContext ctx = SpringApplication.run
         (Application.class,args);
        }
     }

上一段代码是一个简单的 Java main 方法,在 SpringApplication 类上执行静态 run 方法。

SpringApplication 类

SpringApplication 类可用于从 Java main 方法中引导和启动 Spring 应用程序。

以下是在 Spring Boot 应用程序启动时通常执行的步骤:

  1. 创建 Spring 的 ApplicationContext 实例。

  2. 启用接受命令行参数并将其作为 Spring 属性公开的功能。

  3. 根据配置加载所有 Spring Bean。

@SpringBootApplication 注解

@SpringBootApplication 注解是三个注解的快捷方式:

  • @Configuration:表示这是一个 Spring 应用程序上下文配置文件。

  • @EnableAutoConfiguration:启用自动配置,这是 Spring Boot 的重要功能。我们将在单独的部分中稍后讨论自动配置。

  • @ComponentScan:启用在此类及其所有子包中扫描 Spring bean。

运行我们的 Hello World 应用程序

我们可以用多种方式运行 Hello World 应用程序。让我们从最简单的方式开始运行它--以 Java 应用程序的方式运行。在你的 IDE 中,右键单击应用程序,然后以Java 应用程序的方式运行它。以下截图显示了运行我们的Hello World应用程序的一些日志:

运行我们的 Hello World 应用程序

以下是需要注意的关键事项:

  • Tomcat 服务器在端口8080上启动--Tomcat 在端口(s): 8080 (http)上启动

  • DispatcherServlet已配置。这意味着 Spring MVC 框架已准备好接受请求--将 servlet: 'dispatcherServlet'映射到[/]

  • 默认启用了四个过滤器--characterEncodingFilterhiddenHttpMethodFilterhttpPutFormContentFilterrequestContextFilter

  • 默认错误页面已配置--将"{[/error]}"映射到公共 org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)

  • WebJars 已自动配置。WebJars 使静态依赖项(如 Bootstrap 和查询)的依赖项管理成为可能--将 URL 路径[/webjars/**]映射到类型为[class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]的处理程序`

以下截图显示了当前的应用程序布局。我们只有两个文件,pom.xmlApplication.java

运行我们的 Hello World 应用程序

仅使用一个简单的pom.xml文件和一个 Java 类,我们就能够启动 Spring MVC 应用程序,并具有前面描述的所有功能。关于 Spring Boot 最重要的东西是理解后台发生了什么。理解前面的启动日志是第一步。让我们看看 Maven 依赖项以获得更清晰的了解。

以下截图显示了我们在创建的pom.xml文件中的基本配置中配置的一些依赖项:

运行我们的 Hello World 应用程序

Spring Boot 做了很多魔法。一旦你的应用程序配置并运行,我建议你玩一玩它,以获得更深入的理解,这将有助于你在调试问题时。

正如蜘蛛侠所说,能力越大,责任越大。这在 Spring Boot 的情况下绝对正确。在未来的时间里,最好的 Spring Boot 开发者将是那些理解后台发生什么的人--依赖项和自动配置。

自动配置

为了让我们进一步理解自动配置,让我们扩展我们的应用程序类以包含更多代码行:

    ApplicationContext ctx = SpringApplication.run(Application.class,
     args);
    String[] beanNames = ctx.getBeanDefinitionNames();
    Arrays.sort(beanNames);

   for (String beanName : beanNames) {
     System.out.println(beanName);
    }

我们获取在 Spring 应用程序上下文中定义的所有 bean 并打印它们的名称。当Application.java作为 Java 程序运行时,它将打印 bean 列表,如下面的输出所示:

application
basicErrorController
beanNameHandlerMapping
beanNameViewResolver
characterEncodingFilter
conventionErrorViewResolver
defaultServletHandlerMapping
defaultViewResolver
dispatcherServlet
dispatcherServletRegistration
duplicateServerPropertiesDetector
embeddedServletContainerCustomizerBeanPostProcessor
error
errorAttributes
errorPageCustomizer
errorPageRegistrarBeanPostProcessor
faviconHandlerMapping
faviconRequestHandler
handlerExceptionResolver
hiddenHttpMethodFilter
httpPutFormContentFilter
httpRequestHandlerAdapter
jacksonObjectMapper
jacksonObjectMapperBuilder
jsonComponentModule
localeCharsetMappingsCustomizer
mappingJackson2HttpMessageConverter
mbeanExporter
mbeanServer
messageConverters
multipartConfigElement
multipartResolver
mvcContentNegotiationManager
mvcConversionService
mvcPathMatcher
mvcResourceUrlProvider
mvcUriComponentsContributor
mvcUrlPathHelper
mvcValidator
mvcViewResolver
objectNamingStrategy
autoconfigure.AutoConfigurationPackages
autoconfigure.PropertyPlaceholderAutoConfiguration
autoconfigure.condition.BeanTypeRegistry
autoconfigure.context.ConfigurationPropertiesAutoConfiguration
autoconfigure.info.ProjectInfoAutoConfiguration
autoconfigure.internalCachingMetadataReaderFactory
autoconfigure.jackson.JacksonAutoConfiguration
autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration
autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration
autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration
autoconfigure.jmx.JmxAutoConfiguration
autoconfigure.web.DispatcherServletAutoConfiguration
autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
autoconfigure.web.EmbeddedServletContainerAutoConfiguration
autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat
autoconfigure.web.ErrorMvcAutoConfiguration
autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration
autoconfigure.web.HttpEncodingAutoConfiguration
autoconfigure.web.HttpMessageConvertersAutoConfiguration
autoconfigure.web.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration
autoconfigure.web.JacksonHttpMessageConvertersConfiguration
autoconfigure.web.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration
autoconfigure.web.MultipartAutoConfiguration
autoconfigure.web.ServerPropertiesAutoConfiguration
autoconfigure.web.WebClientAutoConfiguration
autoconfigure.web.WebClientAutoConfiguration$RestTemplateConfiguration
autoconfigure.web.WebMvcAutoConfiguration
autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration
autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter
autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration
autoconfigure.websocket.WebSocketAutoConfiguration
autoconfigure.websocket.WebSocketAutoConfiguration$TomcatWebSocketConfiguration
context.properties.ConfigurationPropertiesBindingPostProcessor
context.properties.ConfigurationPropertiesBindingPostProcessor.store
annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
annotation.ConfigurationClassPostProcessor.importAwareProcessor
annotation.internalAutowiredAnnotationProcessor
annotation.internalCommonAnnotationProcessor
annotation.internalConfigurationAnnotationProcessor
annotation.internalRequiredAnnotationProcessor
event.internalEventListenerFactory
event.internalEventListenerProcessor
preserveErrorControllerTargetClassPostProcessor
propertySourcesPlaceholderConfigurer
requestContextFilter
requestMappingHandlerAdapter
requestMappingHandlerMapping
resourceHandlerMapping
restTemplateBuilder
serverProperties
simpleControllerHandlerAdapter
spring.http.encoding-autoconfigure.web.HttpEncodingProperties
spring.http.multipart-autoconfigure.web.MultipartProperties
spring.info-autoconfigure.info.ProjectInfoProperties
spring.jackson-autoconfigure.jackson.JacksonProperties
spring.mvc-autoconfigure.web.WebMvcProperties
spring.resources-autoconfigure.web.ResourceProperties
standardJacksonObjectMapperBuilderCustomizer
stringHttpMessageConverter
tomcatEmbeddedServletContainerFactory
viewControllerHandlerMapping
viewResolver
websocketContainerCustomizer

需要考虑的重要事项如下:

  • 这些 bean 在哪里定义?

  • 这些 bean 是如何创建的?

这就是 Spring 自动配置的魔力。

每当我们向 Spring Boot 项目添加一个新的依赖项时,Spring Boot 自动配置会自动尝试根据依赖项配置 bean。

例如,当我们向spring-boot-starter-web添加依赖项时,以下 bean 将被自动配置:

  • basicErrorControllerhandlerExceptionResolver:它是基本的异常处理。当发生异常时,它显示默认的错误页面。

  • beanNameHandlerMapping:它用于解析到处理器(控制器)的路径。

  • characterEncodingFilter:它提供默认字符编码 UTF-8。

  • dispatcherServlet:它是 Spring MVC 应用程序的前端控制器。

  • jacksonObjectMapper:它在 REST 服务中将对象转换为 JSON,并将 JSON 转换为对象。

  • messageConverters:它是默认的消息转换器,用于将对象转换为 XML 或 JSON,反之亦然。

  • multipartResolver:它为 Web 应用程序提供上传文件的支持。

  • mvcValidator:它支持 HTTP 请求的验证。

  • viewResolver:它将逻辑视图名称解析为物理视图。

  • propertySourcesPlaceholderConfigurer:它支持应用程序配置的外部化。

  • requestContextFilter:它默认为请求设置过滤器。

  • restTemplateBuilder:它用于调用 REST 服务。

  • tomcatEmbeddedServletContainerFactory:Tomcat 是 Spring Boot 基于 Web 应用程序的默认嵌入式 servlet 容器。

在下一节中,让我们看看一些入门项目和它们提供的自动配置。

入门项目

下表显示了 Spring Boot 提供的一些重要入门项目:

入门项目 描述
spring-boot-starter-webservices 这是一个用于开发基于 XML 的 Web 服务的入门项目。
spring-boot-starter-web 这是一个用于构建基于 Spring MVC 的 Web 应用程序或 RESTful 应用程序的入门项目。它使用 Tomcat 作为默认的嵌入式 servlet 容器。
spring-boot-starter-activemq 这支持在 ActiveMQ 上使用 JMS 进行基于消息的通信。
spring-boot-starterintegration 这支持提供企业集成模式实现的 Spring Integration 框架。
spring-boot-starter-test 这提供了对各种单元测试框架的支持,例如 JUnit、Mockito 和 Hamcrest 匹配器。
spring-boot-starter-jdbc 这提供了使用 Spring JDBC 的支持。它默认配置了一个 Tomcat JDBC 连接池。
spring-boot-startervalidation 这提供了对 Java Bean Validation API 的支持。它的默认实现是 hibernate-validator。
spring-boot-starter-hateoas HATEOAS 代表超媒体作为应用程序状态引擎。使用 HATEOAS 的 RESTful 服务除了数据外,还返回与当前上下文相关的附加资源的链接。
spring-boot-starter-jersey JAX-RS 是 Java EE 标准用于开发 REST API。Jersey 是默认实现。此启动项目提供了基于 JAX-RS 的 REST API 的支持。
spring-boot-starter-websocket HTTP 是无状态的。WebSockets 允许你保持服务器和浏览器之间的连接。此启动项目提供了对 Spring WebSockets 的支持。
spring-boot-starter-aop 这提供了对面向方面编程的支持。它还提供了对 AspectJ 的支持,用于高级面向方面编程。
spring-boot-starter-amqp 以 RabbitMQ 为默认,此启动项目提供了基于 AMQP 的消息传递。
spring-boot-starter-security 此启动项目使 Spring Security 的自动配置成为可能。
spring-boot-starter-data-jpa 这提供了对 Spring Data JPA 的支持。其默认实现是 Hibernate。
spring-boot-starter 这是 Spring Boot 应用程序的基础启动项目。它提供了自动配置和日志记录的支持。
spring-boot-starter-batch 这提供了使用 Spring Batch 开发批处理应用程序的支持。
spring-boot-starter-cache 这是使用 Spring 框架进行缓存的基本支持。
spring-boot-starter-data-rest 这是使用 Spring Data REST 暴露 REST 服务的支持。

到目前为止,我们已经设置了一个基本的 Web 应用程序,并理解了与 Spring Boot 相关的一些重要概念:

  • 自动配置

  • 启动项目

  • spring-boot-maven-plugin

  • spring-boot-starter-parent

  • 注解 @SpringBootApplication

现在,让我们将重点转向理解 REST 是什么以及如何构建 REST 服务。

什么是 REST?

表征状态转移 (REST) 基本上是一种网络架构风格。REST 规定了一系列约束。这些约束确保客户端(服务消费者和浏览器)可以以灵活的方式与服务器交互。

让我们先了解一些常见的术语:

  • 服务器:服务提供者。提供客户端可以消费的服务。

  • 客户端:服务消费者。可能是浏览器或另一个系统。

  • 资源:任何信息都可以是资源:一个人、一张图片、一个视频或你想要出售的产品。

  • 表示:资源可以表示的特定方式。例如,产品资源可以使用 JSON、XML 或 HTML 来表示。不同的客户端可能会请求资源的不同表示形式。

以下是一些重要的 REST 约束:

  • 客户端-服务器:应该有一个服务器(服务提供者)和一个客户端(服务消费者)。这使服务器和客户端能够随着新技术的出现而松散耦合和独立演进。

  • 无状态:每个服务应该是无状态的。后续请求不应依赖于之前请求中临时存储的一些数据。消息应该是自我描述的。

  • 统一接口:每个资源都有一个资源标识符。在 Web 服务的情况下,我们使用这个 URI 示例:/users/Jack/todos/1。在这里,URI 中的 Jack 是用户的名称,1 是我们想要检索的待办事项的 ID。

  • 可缓存:服务响应应该是可缓存的。每个响应都应该指示它是否可缓存。

  • 分层系统:服务的消费者不应假设与服务提供者有直接连接。由于请求可以被缓存,客户端可能会从中间层获取缓存的响应。

  • 通过表示修改资源:资源可以有多个表示。应该可以通过带有这些表示之一的消息来修改资源。

  • 超媒体作为应用程序状态引擎HATEOAS):RESTful 应用程序的消费者应该只知道一个固定的服务 URL。所有后续资源都应该可以从资源表示中包含的链接中找到。

这里显示了带有 HATEOAS 链接的示例响应。这是请求检索所有待办事项的响应:

    {  
"_embedded":{ 
"todos":[ 
{ 
"user":"Jill",
"desc":"Learn Hibernate",
"done":false,
"_links":{ 
"self":{ 
"href":"http://localhost:8080/todos/1"
                  },
"todo":{ 
"href":"http://localhost:8080/todos/1"
}
}
}
]
},
"_links":{ 
"self":{ 
"href":"http://localhost:8080/todos"
},
"profile":{ 
"href":"http://localhost:8080/profile/todos"
},
"search":{ 
"href":"http://localhost:8080/todos/search"
}
}
    }

前面的响应包括以下链接:

  • 特定的待办事项(http://localhost:8080/todos/1

  • 搜索 resourcehttp://localhost:8080/todos/search

如果服务消费者想要进行搜索,它可以选择从响应中获取搜索 URL 并向其发送搜索请求。这将减少服务提供者与服务消费者之间的耦合。

我们最初开发的服务将不会遵守所有这些约束。随着我们进入下一课,我们将向您介绍这些约束的细节并将它们添加到服务中,使它们更加 RESTful。

第一个 REST 服务

让我们从创建一个简单的 REST 服务开始,该服务返回一个欢迎消息。我们将创建一个简单的 POJO WelcomeBean 类,其中有一个名为 message 的成员字段和一个参数构造函数,如下面的代码片段所示:

    package com.mastering.spring.springboot.bean;

    public class WelcomeBean {
      private String message;

       public WelcomeBean(String message) {
         super();
         this.message = message;
       }

      public String getMessage() {
        return message;
      }
    }

简单返回字符串的方法

让我们从创建一个简单的 REST 控制器方法返回一个字符串开始:

    @RestController
    public class BasicController {
      @GetMapping("/welcome")
      public String welcome() {
        return "Hello World";
      }
    }

以下是一些需要注意的重要事项:

  • @RestController@RestController 注解提供了 @ResponseBody@Controller 注解的组合。这通常用于创建 REST 控制器。

  • @GetMapping("welcome")@GetMapping@RequestMapping(method = RequestMethod.GET) 的快捷方式。这个注解是一个可读的替代方案。带有此注解的方法将处理对 welcome URI 的 GET 请求。

如果我们将 Application.java 作为 Java 应用程序运行,它将启动嵌入的 Tomcat 容器。我们可以在浏览器中打开这个 URL,如下面的截图所示:

简单返回字符串的方法

单元测试

让我们快速编写一个单元测试来测试前面的controller方法:

    @RunWith(SpringRunner.class)
    @WebMvcTest(BasicController.class)
    public class BasicControllerTest {

      @Autowired
      private MockMvc mvc;

      @Test
      public void welcome() throws Exception {
        mvc.perform(
        MockMvcRequestBuilders.get("/welcome")
       .accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk())
       .andExpect(content().string(
       equalTo("Hello World")));
      }
    }

在前面的单元测试中,我们将使用BasicController启动一个 Mock MVC 实例。以下是一些需要注意的快速事项:

  • @RunWith(SpringRunner.class): SpringRunner 是SpringJUnit4ClassRunner注解的快捷方式。这将为单元测试启动一个简单的 Spring 上下文。

  • @WebMvcTest(BasicController.class): 这个注解可以与 SpringRunner 一起使用来编写简单的 Spring MVC 控制器测试。这将只加载带有 Spring-MVC 相关注解的 bean。在这个例子中,我们使用 BasicController 作为测试类启动一个 Web MVC 测试上下文。

  • @Autowired private MockMvc mvc: 自动装配可以用来发送请求的 MockMvc bean。

  • mvc.perform(MockMvcRequestBuilders.get("/welcome").accept(MediaType.APPLICATION_JSON)): 执行一个带有Accept头值为application/json的请求到/welcome

  • andExpect(status().isOk()): 期望响应的状态是 200(成功)。

  • andExpect(content().string(equalTo("Hello World"))): 期望响应的内容等于"Hello World"

集成测试

当我们进行集成测试时,我们希望启动带有所有配置的控制器和 bean 的嵌入式服务器。以下代码片段显示了如何创建一个简单的集成测试:

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Application.class, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class BasicControllerIT {

      private static final String LOCAL_HOST = 
      "http://localhost:";

      @LocalServerPort
      private int port;

      private TestRestTemplate template = new TestRestTemplate();

      @Test
      public void welcome() throws Exception {
        ResponseEntity<String> response = template
       .getForEntity(createURL("/welcome"), String.class);
        assertThat(response.getBody(), equalTo("Hello World"));
       }

      private String createURL(String uri) {
        return LOCAL_HOST + port + uri;
      }
    }

以下是一些需要注意的重要事项:

  • @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT): 它在 Spring TestContext之上提供了额外的功能。提供了配置完全运行容器和TestRestTemplate(执行请求)端口的支撑。

  • @LocalServerPort private int port: SpringBootTest将确保容器运行的端口自动装配到port变量中。

  • private String createURL(String uri): 该方法将本地主机 URL 和端口附加到 URI 以创建一个完整的 URL。

  • private TestRestTemplate template = new TestRestTemplate(): TestRestTemplate通常用于集成测试。它提供了在RestTemplate之上的额外功能,这在集成测试上下文中特别有用。它不会跟随重定向,这样我们就可以断言响应位置。

  • template.getForEntity(createURL("/welcome"), String.class): 它执行了对给定 URI 的 GET 请求。

  • assertThat(response.getBody(), equalTo("Hello World")): 它断言响应体内容为"Hello World"

简单的 REST 方法返回一个对象

在前面的方法中,我们返回了一个字符串。让我们创建一个返回适当 JSON 响应的方法。看看以下方法:

    @GetMapping("/welcome-with-object")
    public WelcomeBean welcomeWithObject() {
      return new WelcomeBean("Hello World");
    }

前面的方法返回了一个初始化为消息"Hello World"的简单WelcomeBean

执行请求

让我们发送一个测试请求并查看我们得到的响应。以下截图显示了输出:

执行请求

对于 http://localhost:8080/welcome-with-object URL 的响应如下:

    {"message":"Hello World"}

需要回答的问题是:我们返回的 WelcomeBean 对象是如何被转换为 JSON 的?

再次强调,这是 Spring Boot 自动配置的魔力。如果一个应用中存在 Jackson,Spring Boot 会自动配置默认的对象到 JSON(反之亦然)转换器实例。

单元测试

让我们快速编写一个检查 JSON 响应的单元测试。让我们将测试添加到 BasicControllerTest

    @Test
    public void welcomeWithObject() throws Exception {
      mvc.perform(
       MockMvcRequestBuilders.get("/welcome-with-object")
      .accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("Hello World")));
    }

此测试与早期的单元测试非常相似,只是我们使用 containsString 来检查内容是否包含子字符串 "Hello World"。我们将在稍后学习如何编写正确的 JSON 测试。

集成测试

让我们把重点转向编写集成测试。让我们在 BasicControllerIT 中添加一个方法,如下面的代码片段所示:

    @Test
    public void welcomeWithObject() throws Exception {
      ResponseEntity<String> response = 
      template.getForEntity(createURL("/welcome-with-object"), 
      String.class);
      assertThat(response.getBody(), 
      containsString("Hello World"));
    }

此方法与早期的集成测试类似,只是我们使用 String 方法断言子字符串。

带有路径变量的 Get 方法

让我们把注意力转向路径变量。路径变量用于将 URI 中的值绑定到控制器方法上的变量。在以下示例中,我们想参数化名字,以便我们可以使用名字自定义欢迎消息:

    private static final String helloWorldTemplate = "Hello World, 
    %s!";

   @GetMapping("/welcome-with-parameter/name/{name}")
   public WelcomeBean welcomeWithParameter(@PathVariable String name) 
    {
       return new WelcomeBean(String.format(helloWorldTemplate, name));
    }

需要注意的几个重要事项如下:

  • @GetMapping("/welcome-with-parameter/name/{name}"): {name} 表示这个值将是变量。一个 URI 中可以有多个变量模板。

  • welcomeWithParameter(@PathVariable String name): @PathVariable 确保从 URI 绑定变量值到变量 name。

  • String.format(helloWorldTemplate, name): 一个简单的字符串格式,用名字替换模板中的 %s

执行请求

让我们发送一个测试请求并查看我们得到的响应。以下截图显示了响应:

执行请求

对于 http://localhost:8080/welcome-with-parameter/name/Buddy URL 的响应如下:

    {"message":"Hello World, Buddy!"}

如预期的那样,URI 中的名字被用来形成响应中的消息。

单元测试

让我们快速编写一个针对前面方法的单元测试。我们希望将名字作为 URI 的一部分传递,并检查响应是否包含该名字。以下代码显示了如何做到这一点:

    @Test
    public void welcomeWithParameter() throws Exception {
      mvc.perform(
      MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy")
     .accept(MediaType.APPLICATION_JSON))
     .andExpect(status().isOk())
     .andExpect(
     content().string(containsString("Hello World, Buddy")));
    }

需要注意的几个重要事项如下:

  • MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy"): 这与 URI 中的变量模板匹配。我们传递名字 Buddy

  • .andExpect(content().string(containsString("Hello World, Buddy"))): 我们期望响应中包含带有名字的消息。

集成测试

前面方法的集成测试非常简单。看看下面的 test 方法:

    @Test
    public void welcomeWithParameter() throws Exception {
      ResponseEntity<String> response = 
      template.getForEntity(
      createURL("/welcome-with-parameter/name/Buddy"), String.class);
      assertThat(response.getBody(), 
      containsString("Hello World, Buddy"));
    }

需要注意的几个重要事项如下:

  • createURL("/welcome-with-parameter/name/Buddy"):这与 URI 中的变量模板相匹配。我们传递了名称,Buddy。

  • assertThat(response.getBody(), containsString("Hello World, Buddy")):我们期望响应包含带有名称的消息。

在本节中,我们探讨了使用 Spring Boot 创建简单 REST 服务的基础知识。我们还确保了拥有良好的单元测试和集成测试。虽然这些非常基础,但它们为我们在下一节中构建的更复杂的 REST 服务奠定了基础。

我们实现的单元测试和集成测试可以使用 JSON 比较而不是简单的子字符串比较来有更好的断言。我们将在下一节中为我们将创建的 REST 服务编写的测试中关注这一点。

创建待办事项资源

我们将专注于创建基本的待办事项管理系统中的 REST 服务。我们将为以下内容创建服务:

  • 检索特定用户的待办事项列表

  • 检索特定待办事项的详细信息

  • 为用户创建待办事项

请求方法、操作和 URI

REST 服务的最佳实践之一是根据我们执行的操作使用适当的 HTTP 请求方法。在我们至今公开的服务中,我们使用了 GET 方法,因为我们专注于读取数据的服务。

下表显示了根据我们执行的操作适当的 HTTP 请求方法:

HTTP 请求方法 操作
GET 读取--检索资源的详细信息
POST 创建--创建一个新的条目或资源
PUT 更新/替换
PATCH 更新/修改资源的一部分
DELETE 删除

让我们快速将我们想要创建的服务映射到适当的请求方法:

  • 检索特定用户的待办事项列表:这是读取操作。我们将使用 GET 方法。我们将使用 URI:/users/{name}/todos。另一个好的做法是在 URI 中使用复数形式表示静态事物:users、todo 等等。这会产生更易读的 URI。

  • 检索特定待办事项的详细信息:同样,我们将使用 GET 方法。我们将使用 URI /users/{name}/todos/{id}。你可以看到这与我们之前决定的待办事项列表的 URI 一致。

  • 为用户创建待办事项:对于创建操作,建议的 HTTP 请求方法是 POST。要创建一个新的待办事项,我们将向 URI /users/{name}/todos 发送 POST 请求。

Bean 和服务

为了能够检索和存储待办事项的详细信息,我们需要一个待办事项 Bean 和一个用于检索和存储详细信息的服务。

让我们创建一个待办事项 Bean:

    public class Todo {
      private int id;
      private String user;

      private String desc;

      private Date targetDate;
      private boolean isDone;

      public Todo() {}

      public Todo(int id, String user, String desc, 
      Date targetDate, boolean isDone) { 
        super();
        this.id = id;
        this.user = user;
        this.desc = desc;
        this.targetDate = targetDate;
        this.isDone = isDone;
      }

       //ALL Getters
    }

我们创建了一个简单的待办事项 Bean,包含 ID、用户名称、待办事项描述、待办事项目标日期和完成状态指示器。我们为所有字段添加了构造函数和获取器。

现在让我们添加 TodoService

   @Service
   public class TodoService {
     private static List<Todo> todos = new ArrayList<Todo>();
     private static int todoCount = 3;

     static {
       todos.add(new Todo(1, "Jack", "Learn Spring MVC", 
       new Date(), false));
       todos.add(new Todo(2, "Jack", "Learn Struts", new Date(), 
       false));
       todos.add(new Todo(3, "Jill", "Learn Hibernate", new Date(), 
       false));
      }

     public List<Todo> retrieveTodos(String user) {
       List<Todo> filteredTodos = new ArrayList<Todo>();
       for (Todo todo : todos) {
         if (todo.getUser().equals(user))
         filteredTodos.add(todo);
        }
      return filteredTodos;
     }

    public Todo addTodo(String name, String desc, 
    Date targetDate, boolean isDone) {
      Todo todo = new Todo(++todoCount, name, desc, targetDate, 
      isDone);
      todos.add(todo);
      return todo;
    }

    public Todo retrieveTodo(int id) {
      for (Todo todo : todos) {
      if (todo.getId() == id)
        return todo;
      }
      return null;
     }
   }

需要注意的快速事项如下:

  • 为了保持简单,此服务不与数据库通信。它维护一个内存中的待办事项数组列表。此列表使用静态初始化器初始化。

  • 我们公开了一些简单的检索方法和一个添加待办事项的方法。

既然我们已经准备好了服务和 bean,我们可以创建我们的第一个服务来检索用户的待办事项列表。

检索待办事项列表

我们将创建一个新的RestController注解,称为TodoController。检索待办事项方法的代码如下所示:

    @RestController
    public class TodoController {
     @Autowired
     private TodoService todoService;

     @GetMapping("/users/{name}/todos")
     public List<Todo> retrieveTodos(@PathVariable String name) {
       return todoService.retrieveTodos(name);
     }
    }

以下是一些需要注意的事项:

  • 我们使用@Autowired注解自动装配待办事项服务

  • 我们使用@GetMapping注解将"/users/{name}/todos" URI 的 GET 请求映射到retrieveTodos方法

执行服务

让我们发送一个测试请求并查看我们得到什么响应。以下截图显示了输出:

执行服务

http://localhost:8080/users/Jack/todos URL 的响应如下:

   [
    {"id":1,"user":"Jack","desc":"Learn Spring    
     MVC","targetDate":1481607268779,"done":false},  
    {"id":2,"user":"Jack","desc":"Learn 
    Struts","targetDate":1481607268779, "done":false}
   ]

单元测试

测试TodoController类的代码截图如下:

   @RunWith(SpringRunner.class)
   @WebMvcTest(TodoController.class)
   public class TodoControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private TodoService service;

    @Test
    public void retrieveTodos() throws Exception {
     List<Todo> mockList = Arrays.asList(new Todo(1, "Jack",
     "Learn Spring MVC", new Date(), false), new Todo(2, "Jack",
     "Learn Struts", new Date(), false));

     when(service.retrieveTodos(anyString())).thenReturn(mockList);

     MvcResult result = mvc
    .perform(MockMvcRequestBuilders.get("/users
    /Jack/todos").accept(MediaType.APPLICATION_JSON))
    .andExpect(status().isOk()).andReturn();

    String expected = "["
     + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" +","
     + "{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";

     JSONAssert.assertEquals(expected, result.getResponse()
      .getContentAsString(), false);
     }
    }

一些需要注意的重要事项如下:

  • 我们正在编写一个单元测试。因此,我们只想测试TodoController类中存在的逻辑。所以,我们使用@WebMvcTest(TodoController.class)初始化一个只包含TodoController类的 Mock MVC 框架。

  • @MockBean private TodoService service:我们使用@MockBean注解模拟TodoService。在运行SpringRunner的测试类中,使用@MockBean定义的 bean 将被 Mockito 框架创建的模拟所替换。

  • when(service.retrieveTodos(anyString())).thenReturn(mockList):我们正在模拟retrieveTodos服务方法以返回模拟列表。

  • MvcResult result = ..:我们将请求的结果接受到 MvcResult 变量中,以便我们可以在响应上执行断言。

  • JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false):JSONAssert 是一个非常有用的框架,用于对 JSON 执行断言。它将响应文本与预期值进行比较。JSONAssert足够智能,可以忽略未指定的值。另一个优点是在断言失败时有一个清晰的失败消息。最后一个参数 false 表示使用非严格模式。如果将其更改为 true,则预期值应与结果完全匹配。

集成测试

在以下代码片段中显示了在TodoController类上执行集成测试的代码。它启动了包含所有控制器和 bean 的整个 Spring 上下文:

   @RunWith(SpringJUnit4ClassRunner.class)
   @SpringBootTest(classes = Application.class, webEnvironment =     
   SpringBootTest.WebEnvironment.RANDOM_PORT)
   public class TodoControllerIT {

    @LocalServerPort
    private int port;

    private TestRestTemplate template = new TestRestTemplate();

    @Test
    public void retrieveTodos() throws Exception {
     String expected = "["
     + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" + ","
     + "{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";

     String uri = "/users/Jack/todos";

     ResponseEntity<String> response =
     template.getForEntity(createUrl(uri), String.class);

     JSONAssert.assertEquals(expected, response.getBody(), false);
    }

     private String createUrl(String uri) {
     return "http://localhost:" + port + uri;
    }
  }

这个测试与BasicController的集成测试非常相似,除了我们使用JSONAssert来断言响应。

检索特定待办事项的详细信息

我们现在将添加一个用于检索特定待办事项详细信息的函数:

    @GetMapping(path = "/users/{name}/todos/{id}")
    public Todo retrieveTodo(@PathVariable String name, @PathVariable 
    int id) {
      return todoService.retrieveTodo(id);
    }

以下是一些需要注意的事项:

  • 映射的 URI 是/users/{name}/todos/{id}

  • 我们为 nameid 定义了两个路径变量

执行服务

让我们发送一个测试请求,看看我们会得到什么响应,如下面的截图所示:

执行服务

对于 http://localhost:8080/users/Jack/todos/1 URL 的响应如下所示:

    {"id":1,"user":"Jack","desc":"Learn Spring MVC", 
    "targetDate":1481607268779,"done":false}

单元测试

单元测试 retrieveTodo 的代码如下:

     @Test
     public void retrieveTodo() throws Exception {
       Todo mockTodo = new Todo(1, "Jack", "Learn Spring MVC", 
       new Date(), false);

       when(service.retrieveTodo(anyInt())).thenReturn(mockTodo);

       MvcResult result = mvc.perform(
       MockMvcRequestBuilders.get("/users/Jack/todos/1")
       .accept(MediaType.APPLICATION_JSON))
       .andExpect(status().isOk()).andReturn();

       String expected = "{id:1,user:Jack,desc:\"Learn Spring
       MVC\",done:false}";

      JSONAssert.assertEquals(expected, 
       result.getResponse().getContentAsString(), false);

     }

需要注意的几个重要事项如下:

  • when(service.retrieveTodo(anyInt())).thenReturn(mockTodo): 我们正在模拟 retrieveTodo 服务方法以返回模拟的 Todo。

  • MvcResult result = ..: 我们将请求的结果接受到 MvcResult 变量中,以便我们可以在响应上执行断言。

  • JSONAssert.assertEquals(expected, result.getResponse().getContentAsString(), false): 断言结果是否符合预期。

集成测试

TodoController 中对 retrieveTodos 进行集成测试的代码如下。这将添加到 TodoControllerIT 类中:

     @Test
     public void retrieveTodo() throws Exception {
       String expected = "{id:1,user:Jack,desc:\"Learn Spring   
       MVC\",done:false}";
       ResponseEntity<String> response = template.getForEntity(
       createUrl("/users/Jack/todos/1"), String.class);
       JSONAssert.assertEquals(expected, response.getBody(), false);
     }

添加 Todo

我们现在将添加创建新 Todo 的方法。用于创建的 HTTP 方法是 POST。我们将向 "/users/{name}/todos" URI 发送 POST 请求:

    @PostMapping("/users/{name}/todos")
    ResponseEntity<?> add(@PathVariable String name,
    @RequestBody Todo todo) { 
      Todo createdTodo = todoService.addTodo(name, todo.getDesc(),
      todo.getTargetDate(), todo.isDone());
      if (createdTodo == null) {
         return ResponseEntity.noContent().build();
      }

     URI location = ServletUriComponentsBuilder.fromCurrentRequest()

    .path("/{id}").buildAndExpand(createdTodo.getId()).toUri();
    return ResponseEntity.created(location).build();
   }

需要注意的几个事项如下:

  • @PostMapping("/users/{name}/todos"): @PostMapping 注解将 add() 方法映射到使用 POST 方法的 HTTP 请求。

  • ResponseEntity<?> add(@PathVariable String name, @RequestBody Todo todo): 一个 HTTP POST 请求理想情况下应返回创建资源的 URI。我们使用 ResourceEntity 来做这件事。@RequestBody 将请求体直接绑定到 Bean。

  • ResponseEntity.noContent().build(): 用于返回资源创建失败。

  • ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(createdTodo.getId()).toUri(): 形成可以返回在响应中的创建资源的 URI。

  • ResponseEntity.created(location).build(): 返回状态为 201(CREATED) 并带有创建的资源链接。

Postman

如果你使用的是 Mac,你可能还想尝试 Paw 应用程序。

让我们发送一个测试请求并查看我们得到的响应。以下截图显示了响应:

Postman

我们将使用 Postman 应用程序与 REST 服务进行交互。您可以从网站安装它,www.getpostman.com/。它在 Windows 和 Mac 上可用。还有一个 Google Chrome 插件。

执行 POST 服务

要使用 POST 方法创建一个新的 Todo,我们需要在请求体中包含 Todo 的 JSON。以下截图显示了我们可以如何使用 Postman 应用程序创建请求和执行请求后的响应:

执行 POST 服务

需要注意的几个重要事项如下:

  • 我们正在发送一个 POST 请求。因此,我们从左上角的下拉菜单中选择 POST

  • 要将 Todo JSON 作为请求体的一部分发送,我们在Body选项卡中选择raw选项(用蓝色圆点突出显示)。我们选择内容类型为 JSON (application/json)。

  • 请求成功执行后,您可以在屏幕中间的栏中看到请求的状态:“状态:201 已创建”。

  • 位置是 http://localhost:8080/users/Jack/todos/5。这是响应中接收到的新的 todo 的 URI。

请求 http://localhost:8080/users/Jack/todos 的完整细节如下所示:

    Header
    Content-Type:application/json

   Body
    {
      "user": "Jack",
      "desc": "Learn Spring Boot",
       "done": false
     }

单元测试

单元测试创建的 Todo 的代码如下所示:

    @Test
    public void createTodo() throws Exception {
     Todo mockTodo = new Todo(CREATED_TODO_ID, "Jack", 
     "Learn Spring MVC", new Date(), false);
     String todo = "{"user":"Jack","desc":"Learn Spring MVC",     
     "done":false}";

    when(service.addTodo(anyString(), anyString(),   
    isNull(),anyBoolean()))
    .thenReturn(mockTodo);

   mvc
    .perform(MockMvcRequestBuilders.post("/users/Jack/todos")
    .content(todo)
    .contentType(MediaType.APPLICATION_JSON)
    )
    .andExpect(status().isCreated())
    .andExpect(
      header().string("location",containsString("/users/Jack/todos/"
     + CREATED_TODO_ID)));
   }

以下是一些需要注意的重要事项:

  • String todo = "{"user":"Jack","desc":"Learn Spring MVC","done":false}":要发送到创建 todo 服务的 Todo 内容。

  • when(service.addTodo(anyString(), anyString(), isNull(), anyBoolean())).thenReturn(mockTodo):模拟服务以返回一个虚拟的 todo。

  • MockMvcRequestBuilders.post("/users/Jack/todos").content(todo).contentType(MediaType.APPLICATION_JSON)):创建一个具有给定内容类型的 POST 请求到指定的 URI。

  • andExpect(status().isCreated()):期望状态为已创建。

  • andExpect(header().string("location",containsString("/users/Jack/todos/" + CREATED_TODO_ID))):期望头部包含带有创建资源的 URI 的 location

集成测试

TodoController 中对创建的 todo 进行集成测试的代码如下所示。这将添加到 TodoControllerIT 类中,如下所示:

    @Test
    public void addTodo() throws Exception {
      Todo todo = new Todo(-1, "Jill", "Learn Hibernate", new Date(),  
      false);
      URI location = template
     .postForLocation(createUrl("/users/Jill/todos"),todo);
      assertThat(location.getPath(), 
      containsString("/users/Jill/todos/4"));
    }

以下是一些需要注意的重要事项:

  • URI location = template.postForLocation(createUrl("/users/Jill/todos"), todo)postForLocation 是一个特别有用的实用方法,尤其是在测试中创建新资源时。我们正在将 todo 发送到给定的 URI,并从头部获取位置。

  • assertThat(location.getPath(), containsString("/users/Jill/todos/4")):断言位置包含指向新创建资源的路径。

Spring Initializr

您想要自动生成 Spring Boot 项目吗?您想要快速开始开发您的应用程序吗?Spring Initializr 就是答案。

Spring Initializr 域名是 start.spring.io。以下截图显示了网站的外观:

Spring Initializr

Spring Initializr 在创建项目方面提供了很多灵活性。您有以下选项:

  • 选择您的构建工具:Maven 或 Gradle。

  • 选择您想要使用的 Spring Boot 版本。

  • 为您的组件配置 Group IDArtifact ID

  • 选择您项目所需的启动器(依赖项)。您可以通过点击屏幕底部的链接,切换到完整版本,查看您可以选择的所有启动器项目。

  • 选择如何打包您的组件:JAR 或 WAR。

  • 选择您想要使用的 Java 版本。

  • 选择您想要使用的 JVM 语言。

以下截图显示了当您展开(点击链接)到完整版本时,Spring Initializr 提供的部分选项:

Spring Initializr

创建您的第一个 Spring Initializr 项目

我们将使用完整版本并输入以下值:

创建您的第一个 Spring Initializr 项目

需要注意的事项如下:

  • 构建工具: Maven

  • Spring Boot 版本: 选择最新可用的

  • : com.mastering.spring

  • 工件: first-spring-initializr

  • 选定的依赖项: 选择 Web, JPA, Actuator 和 Dev Tools。在文本框中输入每个这些,然后按 Enter 键选择它们。我们将在下一节中了解更多关于 Actuator 和 Dev Tools 的信息。

  • Java 版本: 1.8

点击生成项目按钮。这将创建一个 .zip 文件,您可以将其下载到您的计算机上。

以下截图显示了创建的项目结构:

创建您的第一个 Spring Initializr 项目

现在,我们将把这个项目导入到您的 IDE 中。在 Eclipse 中,您可以执行以下步骤:

  1. 启动 Eclipse。

  2. 导航到文件 | 导入

  3. 选择现有的 Maven 项目。

  4. 浏览并选择 Maven 项目的根目录(包含 pom.xml 文件的目录)。

  5. 使用默认设置并点击完成。`

这将把项目导入到 Eclipse 中。以下截图显示了 Eclipse 中项目的结构:

创建您的第一个 Spring Initializr 项目

让我们看看生成项目中的一些重要文件。

pom.xml

以下代码片段显示了声明的依赖项:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

一些其他重要的观察如下:

  • 此组件的打包格式为 .jar

  • org.springframework.boot:spring-boot-starter-parent 被声明为父 POM

  • <java.version>1.8</java.version>: Java 版本是 1.8

  • Spring Boot Maven 插件 (org.springframework.boot:spring-boot-maven-plugin) 被配置为插件

FirstSpringInitializrApplication.java 类

FirstSpringInitializrApplication.java 是 Spring Boot 的启动器:

    package com.mastering.spring;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure
    .SpringBootApplication;

    @SpringBootApplication
    public class FirstSpringInitializrApplication {
       public static void main(String[] args) {
        SpringApplication.run(FirstSpringInitializrApplication.class,   
        args);
      }
    }

FirstSpringInitializrApplicationTests 类

FirstSpringInitializrApplicationTests 包含可以用来开始编写测试的基本上下文,随着我们开始开发应用程序:

    package com.mastering.spring;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class FirstSpringInitializrApplicationTests {

      @Test
      public void contextLoads() {
      }
   }

快速了解自动配置

自动配置是 Spring Boot 最重要的功能之一。在本节中,我们将快速了解幕后,以了解 Spring Boot 自动配置是如何工作的。

大部分 Spring Boot 自动配置的魔法都来自 spring-boot-autoconfigure-{version}.jar。当我们启动任何 Spring Boot 应用程序时,许多 bean 都会自动配置。这是如何发生的?

以下截图显示了从 spring-boot-autoconfigure-{version}.jarspring.factories 的一个摘录。我们出于空间考虑过滤掉了一些配置:

快速浏览自动配置

每次启动 Spring Boot 应用程序时,都会运行前面列出的自动配置类列表。让我们快速查看其中之一:

org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration

这里有一个小的代码片段:

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class WebMvcAutoConfiguration {

需要注意的一些重要点如下:

  • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class }):如果类路径中包含上述提到的任何类,则启用此自动配置。当我们添加一个 Web 启动项目时,我们会引入包含所有这些类的依赖项。因此,此自动配置将被启用。

  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class):只有当应用程序没有显式声明WebMvcConfigurationSupport.class类的 bean 时,此自动配置才会启用。

  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10):这指定了此特定自动配置的优先级。

让我们看看另一个小的代码片段,展示同一类中的一个方法:

    @Bean
    @ConditionalOnBean(ViewResolver.class)
    @ConditionalOnMissingBean(name = "viewResolver", 
    value = ContentNegotiatingViewResolver.class)
    public ContentNegotiatingViewResolver 
    viewResolver(BeanFactory beanFactory) {
      ContentNegotiatingViewResolver resolver = new 
      ContentNegotiatingViewResolver();
      resolver.setContentNegotiationManager
      (beanFactory.getBean(ContentNegotiationManager.class));
      resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
      return resolver;
     }

视图解析器是WebMvcAutoConfiguration类配置的 bean 之一。前面的代码片段确保如果应用程序没有提供视图解析器,那么 Spring Boot 将自动配置一个默认的视图解析器。以下是一些需要注意的重要点:

  • @ConditionalOnBean(ViewResolver.class):如果ViewResolver.class在类路径上,则创建此 bean。

  • @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class):如果没有显式声明名为viewResolver且类型为ContentNegotiatingViewResolver.class的 bean,则创建此 bean。

  • 方法的其他部分在视图解析器中进行配置

总结来说,所有自动配置逻辑都在 Spring Boot 应用程序启动时执行。如果类路径上有特定依赖项或启动项目中的特定类,则执行自动配置类。这些自动配置类查看已经配置的 bean。根据现有的 bean,它们启用默认 bean 的创建。

摘要

Spring Boot 使得基于 Spring 的应用程序开发变得简单。它使我们能够从项目的第一天开始就创建生产就绪的应用程序。

在本课中,我们介绍了 Spring Boot 和 REST 服务的基础知识。我们讨论了 Spring Boot 的不同特性,并创建了一些具有良好测试的 REST 服务。我们通过深入了解自动配置,了解了后台发生了什么。

在下一课中,我们将把注意力转向向 REST 服务添加更多功能。

评估

  1. 可以使用 _______ 类从 Java 主方法启动和运行 Spring 应用程序。

  2. 以下哪个方法用于将本地主机 URL 和端口附加到 URI 以创建完整的 URL?

    1. 私有 URL(String uri)

    2. private String create(String uri)

    3. private String CreateURL(String uri)

    4. private String createURL(String uri)

  3. 判断对错:Tomcat 服务器在端口 8080 上启动--Tomcat 在端口(s):8080 (http) 上启动。

  4. 以下哪个启动模板提供了对各种单元测试框架(如 JUnit、Mockito 和 Hamcrest)的支持?这些框架在 Spring Boot 中负责协调锁竞争。

    1. spring-boot-starter-test

    2. spring-boot-starter-testframe

    3. spring-boot-starter-unittest

    4. spring-boot-starter-testorchestration

  5. 判断对错:multipartResolver 不提供在 Web 应用程序中上传文件的支持。

第二章 扩展微服务

在第 1 课“使用 Spring Boot 构建微服务”中,我们构建了一个提供一些服务的基本组件。在本课中,我们将专注于添加更多功能,使我们的微服务准备好生产。

我们将讨论如何将这些功能添加到我们的微服务中:

  • 异常处理

  • HATEOAS

  • 缓存

  • 国际化

我们还将讨论如何使用 Swagger 记录我们的微服务。我们将查看使用 Spring Security 保护微服务的基础知识。

异常处理

异常处理是开发网络服务的重要部分之一。当出现问题时,我们希望向服务消费者返回一个良好的错误描述。我们不希望服务崩溃而不向服务消费者提供任何有用的信息。

Spring Boot 提供了良好的默认异常处理。在继续自定义它们之前,我们将首先查看 Spring Boot 提供的默认异常处理功能。

Spring Boot 默认异常处理

为了了解 Spring Boot 提供的默认异常处理,让我们从一个不存在的 URL 开始发起请求。

不存在的资源

让我们使用带有头信息(Content-Type:application/json)的GET请求向http://localhost:8080/non-existing-resource发送请求。

以下截图显示了执行请求时的响应:

不存在的资源

响应如下所示:

    {
      "timestamp": 1484027734491,
      "status": 404,
      "error": "Not Found",
      "message": "No message available",
      "path": "/non-existing-resource"
    }

以下是一些需要注意的重要事项:

  • 响应头中的 HTTP 状态为404 - 资源未找到

  • Spring Boot 返回一个有效的 JSON 消息作为响应,消息表明资源未找到

抛出异常的资源

让我们创建一个会抛出异常的资源,并对其发送一个GET请求,以便了解应用程序如何响应运行时异常。

让我们创建一个会抛出异常的虚拟服务。以下代码片段显示了一个简单的服务:

    @GetMapping(path = "/users/dummy-service")
    public Todo errorService() {
      throw new RuntimeException("Some Exception Occured");
    }

以下是一些需要注意的重要事项:

我们正在创建一个 URI 为/users/dummy-serviceGET服务。

服务抛出一个RuntimeException。我们选择RuntimeException以便能够轻松创建异常。如果需要,我们可以轻松地将其替换为自定义异常。

让我们使用 Postman 向http://localhost:8080/users/dummy-service服务发起一个GET请求。响应如下所示:

    {
      "timestamp": 1484028119553,
      "status": 500,
      "error": "Internal Server Error",
      "exception": "java.lang.RuntimeException",
      "message": "Some Exception Occured",
      "path": "/users/dummy-service"
   }

以下是一些需要注意的重要事项:

  • 响应头中的 HTTP 状态为500内部服务器错误

  • Spring Boot 还返回抛出异常的消息

如前两个示例所示,Spring Boot 提供了良好的默认异常处理。在下一节中,我们将专注于了解应用程序如何响应自定义异常。

抛出自定义异常

让我们创建一个自定义异常,并从服务中抛出它。看看下面的代码:

    public class TodoNotFoundException extends RuntimeException {
      public TodoNotFoundException(String msg) {
        super(msg);
      }
    }

这是一段非常简单的代码,它定义了 TodoNotFoundException

现在,让我们增强我们的 TodoController 类,以便在找不到具有给定 ID 的 todo 时抛出 TodoNotFoundException

    @GetMapping(path = "/users/{name}/todos/{id}")
    public Todo retrieveTodo(@PathVariable String name, 
    @PathVariable int id) {
      Todo todo = todoService.retrieveTodo(id);
      if (todo == null) {
        throw new TodoNotFoundException("Todo Not Found");
       }

     return todo;
    }

如果 todoService 返回一个 null 的 todo,我们将抛出 TodoNotFoundException

当我们使用 GET 请求对一个不存在的 todohttp://localhost:8080/users/Jack/todos/222)执行服务时,我们会得到以下代码片段中显示的响应:

    {
      "timestamp": 1484029048788,
      "status": 500,
      "error": "Internal Server Error",
      "exception":    
      "com.mastering.spring.springboot.bean.TodoNotFoundException",
      "message": "Todo Not Found",
      "path": "/users/Jack/todos/222"
    }

如我们所见,一个清晰的异常响应被发送回服务消费者。然而,还有一件事可以进一步改进——响应状态。当资源未找到时,建议您返回一个 404 - 资源未找到 状态。我们将在下一个示例中查看如何自定义响应状态。

自定义异常消息

让我们看看如何自定义前面的异常,并返回带有自定义消息的正确响应状态。

让我们创建一个实体来定义我们自定义异常消息的结构:

    public class ExceptionResponse {
      private Date timestamp = new Date();
      private String message;
      private String details;

      public ExceptionResponse(String message, String details) {
        super();
        this.message = message;
        this.details = details;
       }

      public Date getTimestamp() {
        return timestamp;
      }

      public String getMessage() {
        return message;
      }

      public String getDetails() {
        return details;
      }
     }

我们创建了一个简单的异常响应实体,它具有自动填充的时间戳和一些额外的属性,即消息和详细信息。

TodoNotFoundException 被抛出时,我们希望使用 ExceptionResponse 实体返回一个响应。以下代码展示了我们如何为 TodoNotFoundException.class 创建全局异常处理:

    @ControllerAdvice
    @RestController
    public class RestResponseEntityExceptionHandler 
      extends  ResponseEntityExceptionHandler 
      {
        @ExceptionHandler(TodoNotFoundException.class)
        public final ResponseEntity<ExceptionResponse> 
        todoNotFound(TodoNotFoundException ex) {
           ExceptionResponse exceptionResponse = 
           new ExceptionResponse(  ex.getMessage(), 
           "Any details you would want to add");
           return new ResponseEntity<ExceptionResponse>
           (exceptionResponse, new HttpHeaders(), 
           HttpStatus.NOT_FOUND);
         }
     }

以下是一些需要注意的重要事项:

RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler: 我们正在扩展 ResponseEntityExceptionHandler,这是由 Spring MVC 为集中式异常处理 ControllerAdvice 类提供的基类。

@ExceptionHandler(TodoNotFoundException.class): 这定义了接下来的方法将处理特定的异常 TodoNotFoundException.class。对于没有定义自定义异常处理的任何其他异常,将遵循 Spring Boot 提供的默认异常处理。

ExceptionResponse exceptionResponse = new ExceptionResponse(ex.getMessage(), "Any details you would want to add"): 这将创建一个自定义的异常响应。

new ResponseEntity<ExceptionResponse>(exceptionResponse,new HttpHeaders(), HttpStatus.NOT_FOUND): 这是返回一个 404 资源未找到 响应并使用之前定义的自定义异常的定义。

当我们使用 GET 请求对一个不存在的 todohttp://localhost:8080/users/Jack/todos/222)执行服务时,我们会得到以下响应:

    {
      "timestamp": 1484030343311,
      "message": "Todo Not Found",
      "details": "Any details you would want to add"
    }

如果您想为所有异常创建一个通用的异常消息,我们可以在 RestResponseEntityExceptionHandler 中添加一个带有 @ExceptionHandler(Exception.class) 注解的方法。

以下代码片段展示了我们如何做到这一点:

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<ExceptionResponse> todoNotFound(
    Exception ex) {
       //Customize and return the response
    }

对于没有定义自定义异常处理器的任何异常,将由前面的方法处理。

响应状态

在 REST 服务中,我们需要关注的一个重要问题是错误响应的状态码。以下表格显示了场景和应使用的错误响应状态码:

情况 响应状态
请求体不符合 API 规范。它不包含足够的细节或包含验证错误。 400 BAD REQUEST
认证或授权失败。 401 UNAUTHORIZED
由于各种因素,如超出限制,用户无法执行操作。 403 FORBIDDEN
资源不存在。 404 NOT FOUND
不支持的操作,例如,在只允许 GET 的资源上尝试 POST。 405 METHOD NOT ALLOWED
服务器错误。理想情况下,这种情况不应该发生。消费者无法修复这个问题。 500 INTERNAL SERVER ERROR

在本节中,我们探讨了 Spring Boot 提供的默认异常处理以及如何进一步自定义以满足我们的需求。

HATEOAS

HATEOAS超媒体作为应用状态引擎)是 REST 应用架构的约束之一。

让我们考虑一个服务消费者从服务提供商消费众多服务的情况。开发这种系统的最简单方法是将服务消费者需要从服务提供商获取的每个资源的单个资源 URI 存储起来。然而,这将在服务提供者和服务消费者之间创建紧密耦合。每当服务提供商上的任何资源 URI 发生变化时,服务消费者都需要进行更新。

考虑一个典型的 Web 应用。假设我导航到我的银行账户详情页面。几乎所有的银行网站都会在屏幕上显示我银行账户上所有可能的交易链接,以便我可以通过链接轻松导航。

如果我们能够将一个类似的概念引入 RESTful 服务中,使得服务不仅返回请求资源的有关数据,还提供其他相关资源的详细信息呢?

HATEOAS 将这种为给定资源显示相关链接的概念引入 RESTful 服务。当我们返回特定资源的详细信息时,我们也会返回可以对该资源执行的操作的链接,以及相关资源的链接。如果服务消费者可以使用响应中的链接执行交易,那么它就不需要硬编码所有链接。

Roy Fielding(roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)提出的与 HATEOAS 相关的约束摘录如下:

REST API 不得定义固定的资源名称或层次结构(客户端和服务器之间的明显耦合)。服务器必须拥有控制其自身命名空间的自由。相反,允许服务器指导客户端如何构造适当的 URI,例如在 HTML 表单和 URI 模板中执行,通过在媒体类型和链接关系中定义这些说明。

REST API 应该在没有超出初始 URI(书签)和适用于目标受众的标准化媒体类型(即预期任何可能使用 API 的客户端都能理解)的先验知识的情况下进入。从那时起,所有应用程序状态转换都必须由客户端选择服务器提供的、在接收到的表示中存在或由用户对那些表示的操作暗示的选择来驱动。转换可能由客户端对媒体类型和资源通信机制的了解(两者都可以即时改进,例如按需代码)来确定(或限制)。

这里展示了包含 HATEOAS 链接的示例响应。这是对/todos请求的响应,以检索所有待办事项:

    {
      "_embedded" : {
        "todos" : [ {
          "user" : "Jill",
          "desc" : "Learn Hibernate",
          "done" : false,
         "_links" : {
          "self" : {
                 "href" : "http://localhost:8080/todos/1"
                 },
          "todo" : {
                 "href" : "http://localhost:8080/todos/1"
                  }
            }
     } ]
    },
     "_links" : {
     "self" : {
              "href" : "http://localhost:8080/todos"
              },
     "profile" : {
              "href" : "http://localhost:8080/profile/todos"
              },
     "search" : {
              "href" : "http://localhost:8080/todos/search"
              }
       },
     }

前面的响应包括以下链接:

  • 特定的todoshttp://localhost:8080/todos/1

  • 搜索资源(http://localhost:8080/todos/search

如果服务消费者想要进行搜索,它可以选择从响应中获取搜索 URL,并将搜索请求发送给它。这将减少服务提供者与服务消费者之间的耦合。

在响应中发送 HATEOAS 链接

现在我们已经了解了 HATEOAS 是什么,让我们看看我们如何在响应中发送与资源相关的链接。

Spring Boot Starter HATEOAS

Spring Boot 有一个特定的 HATEOAS 启动器,称为spring-boot-starter-hateoas。我们需要将其添加到pom.xml文件中。

以下代码片段显示了依赖块:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>

spring-boot-starter-hateoas的重要依赖之一是spring-hateoas,它提供了 HATEOAS 功能:

    <dependency>
      <groupId>org.springframework.hateoas</groupId>
      <artifactId>spring-hateoas</artifactId>
    </dependency>

让我们增强retrieveTodo资源(/users/{name}/todos/{id}),使其在响应中返回一个获取所有todos/users/{name}/todos)的链接:

    @GetMapping(path = "/users/{name}/todos/{id}")
    public Resource<Todo> retrieveTodo(
    @PathVariable String name, @PathVariable int id) {
    Todo todo = todoService.retrieveTodo(id);
      if (todo == null) {
           throw new TodoNotFoundException("Todo Not Found");
        }

     Resource<Todo> todoResource = new Resource<Todo>(todo);
     ControllerLinkBuilder linkTo = 
     linkTo(methodOn(this.getClass()).retrieveTodos(name));
     todoResource.add(linkTo.withRel("parent"));

     return todoResource;
    }

需要注意的一些重要点是:

  • ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveTodos(name)):我们想要获取当前类中retrieveTodos方法的链接

  • linkTo.withRel("parent"):与当前资源的关系是父级

以下片段显示了向http://localhost:8080/users/Jack/todos/1发送GET请求时的响应:

   {
     "id": 1,
     "user": "Jack",
     "desc": "Learn Spring MVC",
     "targetDate": 1484038262110,
     "done": false,
     "_links": {
               "parent": {
               "href": "http://localhost:8080/users/Jack/todos"
               }
        }
   }

_links部分将包含所有链接。目前,我们有一个带有关系 parent 和hrefhttp://localhost:8080/users/Jack/todos的链接:

注意

如果你在执行前面的请求时遇到问题,请尝试使用 Accept 头--application/json来执行。

HATEOAS 不是今天大多数资源中常用的东西。然而,它有潜力在减少服务提供者和消费者之间的耦合方面真正有用。

验证

一个好的服务总是在处理数据之前验证数据。在本节中,我们将查看 Bean 验证 API 并使用其参考实现来在我们的服务中实现验证。

Bean 验证 API 提供了一些注解,可以用来验证 Bean。JSR 349规范定义了 Bean 验证 API 1.1。Hibernate-validator 是参考实现;两者都已定义为spring-boot-web-starter项目中的依赖项:

  • hibernate-validator-5.2.4.Final.jar

  • validation-api-1.1.0.Final.jar

我们将为createTodo服务方法创建一个简单的验证。

创建验证涉及两个步骤:

  1. 在控制器方法上启用验证。

  2. 在 Bean 上添加验证

在控制器方法上启用验证

在控制器方法上启用验证非常简单。以下代码片段展示了示例:

    @RequestMapping(method = RequestMethod.POST, 
    path = "/users/{name}/todos")
    ResponseEntity<?> add(@PathVariable String name
    @Valid @RequestBody Todo todo) {

使用@Valid(package javax.validation)注解来标记一个参数进行验证。在add方法执行之前,会执行在Todo Bean 中定义的任何验证。

在 Bean 上定义验证

让我们为Todo Bean 定义一些验证:

   public class Todo {
     private int id; 

     @NotNull
     private String user;

     @Size(min = 9, message = "Enter atleast 10 Characters.")
     private String desc;

以下是一些需要注意的重要点:

  • @NotNull:验证用户字段不为空

  • @Size(min = 9, message = "Enter atleast 10 Characters."):检查desc字段是否至少有九个字符

  • 有许多其他注解可以用来验证 Bean。以下是一些 Bean 验证注解:

  • @AssertFalse@AssertTrue:对于布尔元素。检查被注解的元素。

  • @AssertFalse:检查是否为 false。@Assert检查是否为 true。

  • @Future:被注解的元素必须是一个未来的日期。

  • @Past:被注解的元素必须是一个过去的日期。

  • @Max:被注解的元素必须是一个数值,其值必须小于或等于指定的最大值。

  • @Min:被注解的元素必须是一个数值,其值必须大于或等于指定的最小值。

  • @NotNull:被注解的元素不能为 null。

  • @Pattern:被注解的{@code CharSequence}元素必须匹配指定的正则表达式。正则表达式遵循 Java 正则表达式约定。

  • @Size:被注解的元素大小必须在指定的范围内。

单元测试验证

以下示例展示了我们如何对添加的验证进行单元测试:

     @Test
     public void createTodo_withValidationError() throws Exception {
       Todo mockTodo = new Todo(CREATED_TODO_ID, "Jack", 
       "Learn Spring MVC", new Date(), false);

       String todo = "{"user":"Jack","desc":"Learn","done":false}";

       when( service.addTodo(
         anyString(), anyString(), isNull(), anyBoolean()))
        .thenReturn(mockTodo);

         MvcResult result = mvc.perform(
         MockMvcRequestBuilders.post("/users/Jack/todos")
        .content(todo)
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(
           status().is4xxClientError()).andReturn();
     }

以下是一些需要注意的重要点:

  • "desc":"Learn":我们使用长度为5desc值。这将导致@Size(min = 9, message = "Enter atleast 10 Characters.")检查的验证失败。

  • .andExpect(status().is4xxClientError()):检查验证错误状态。

记录 REST 服务

在服务提供者能够消费服务之前,他们需要一个服务契约。服务契约定义了关于服务的一切细节:

  • 我该如何调用服务?服务的 URI 是什么?

  • 请求格式应该是什么?

  • 我应该期待什么样的响应?

定义 RESTful 服务的服务契约有多种选择。在过去几年中,最受欢迎的是Swagger。Swagger 在过去几年中获得了很大的支持,得到了主要供应商的支持。在本节中,我们将为我们的服务生成 Swagger 文档。

以下是从 Swagger 网站(swagger.io)摘录的引言,定义了 Swagger 规范的目的:

Swagger 规范为您的 API 创建 RESTful 契约,详细说明了所有资源及其操作,以人类和机器可读的格式,便于开发、发现和集成。

生成 Swagger 规范

在过去几年中,RESTful 服务开发中一个有趣的发展趋势是生成服务文档(规范)的工具的演变。这确保了代码和文档始终保持同步。

Springfox Swagger可以用于从 RESTful 服务代码中生成 Swagger 文档。更有甚者,还有一个叫做Swagger UI的出色工具,当集成到应用程序中时,提供了人类可读的文档。

以下代码片段展示了如何将这两个工具添加到pom.xml文件中:

    <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger2</artifactId>
     <version>2.4.0</version>
    </dependency>

    <dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger-ui</artifactId>
     <version>2.4.0</version>
    </dependency>

下一步是添加配置类以启用和生成 Swagger 文档。以下片段展示了如何操作:

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
      @Bean
      public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.any())
        .paths(PathSelectors.any()).build();
      }
    }

以下是一些需要注意的重要点:

  • @Configuration: 定义一个 Spring 配置文件

  • @EnableSwagger2: 启用 Swagger 支持的注解

  • Docket: 一个简单的构建类,用于使用 Swagger Spring MVC 框架配置 Swagger 文档的生成

  • new Docket(DocumentationType.SWAGGER_2): 配置 Swagger 2 作为要使用的 Swagger 版本

  • .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()): 在文档中包含所有 API 和路径

当我们启动服务器时,我们可以启动 API 文档 URL(http://localhost:8080/v2/api-docs)。以下截图显示了部分生成的文档:

生成 Swagger 规范

让我们看看一些生成的文档。以下是检索todos服务的文档:

    "/users/{name}/todos": {
      "get": {
      "tags": [
             "todo-controller"
             ],
      "summary": "retrieveTodos",
      "operationId": "retrieveTodosUsingGET",
      "consumes": [
               "application/json"
               ],
      "produces": [
               "*/*"
               ],
      "parameters": [
              {
                "name": "name",
                "in": "path",
                "description": "name",
                "required": true,
                "type": "string"
              }
             ],
       "responses": {
       "200": {
              "description": "OK",
              "schema": {
                      "type": "array",
                      items": {
                          "$ref": "#/definitions/Todo"
                        }
                       }
               },
       "401": {
                "description": "Unauthorized"
               },
       "403": {
                "description": "Forbidden"
              },
       "404": {
                "description": "Not Found"
              } 
        }
     }

服务定义明确定义了服务的请求和响应。还定义了服务在不同情况下可以返回的不同响应状态。

以下代码片段展示了Todo实体的定义:

    "Resource«Todo»": {
      "type": "object",
      "properties": {
      "desc": {
               "type": "string"
             },
     "done": {
               "type": "boolean"
             },
     "id": {
              "type": "integer",
              "format": "int32"
           },
     "links": {
              "type": "array",
              "items": {
                         "$ref": "#/definitions/Link"
                       }
              },
     "targetDate": {
                    "type": "string",
                    "format": "date-time"
                },
     "user": {
              "type": "string"
            }
        }
      }

它定义了Todo实体中的所有元素,以及它们的格式。

Swagger UI

Swagger UI (http://localhost:8080/swagger-ui.html) 也可以用来查看文档。Swagger UI 通过在之前的步骤中添加的依赖项 (io.springfox:springfox-swagger-ui) 启用。

Swagger UI (petstore.swagger.io) 也可在网上使用。我们可以使用 Swagger UI 可视化任何 Swagger 文档(swagger JSON)。

以下截图显示了暴露控制器的服务列表。当我们点击任何控制器时,它会展开以显示每个控制器支持的请求方法和 URI:

Swagger UI

以下截图显示了在 Swagger UI 中创建用户 todo 的 POST 服务的详细信息:

Swagger UI

以下是一些需要注意的重要事项:

  • Parameters 显示了所有重要的参数,包括请求体

  • Parameter Type 主体(对于 todo 参数)显示了请求主体的预期结构

  • Response Messages 部分显示了服务返回的不同 HTTP 状态码

  • Swagger UI 提供了一种无需额外努力即可公开 API 服务定义的出色方式。

使用注解自定义 Swagger 文档

Swagger UI 还提供了注解来进一步自定义你的文档。

这里列出了检索 todos 服务的部分文档:

    "/users/{name}/todos": {
      "get": {
      "tags": [
             "todo-controller"
             ],
      "summary": "retrieveTodos",
      "operationId": "retrieveTodosUsingGET",
      "consumes": [
               "application/json"
               ],
      "produces": [
                "*/*"
               ],

正如你所见,生成的文档非常原始。我们可以在文档中改进许多地方来更好地描述服务。以下是一些示例:

  • 提供更好的摘要

  • 将 application/JSON 添加到 produces

Swagger 提供了我们可以添加到我们的 RESTful 服务中以自定义文档的注解。让我们添加一些注解到控制器中,以改进文档:

    @ApiOperation(
      value = "Retrieve all todos for a user by passing in his name", 
      notes = "A list of matching todos is returned. Current pagination   
      is not supported.",
      response = Todo.class, 
      responseContainer = "List", 
      produces = "application/json")
      @GetMapping("/users/{name}/todos")
      public List<Todo> retrieveTodos(@PathVariable String name) {
        return todoService.retrieveTodos(name);
     }

以下是一些需要注意的重要点:

  • @ApiOperation(value = "通过传递用户的名字检索所有待办事项"): 在文档中作为服务的摘要生成

  • notes = "返回匹配的待办事项列表。当前不支持分页。": 在文档中作为服务的描述生成

  • produces = "application/json": 自定义服务文档的 produces 部分

更新后的文档摘录如下:

    get": {
         "tags": [
                   "todo-controller"
                 ],
         "summary": "Retrieve all todos for a user by passing in his 
          name",
         "description": "A list of matching todos is returned. Current 
          pagination is not supported.",
         "operationId": "retrieveTodosUsingGET",
         "consumes": [
                     "application/json"
                   ],
         "produces": [
                     "application/json",
                     "*/*"
                   ],

Swagger 提供了许多其他注解来自定义文档。以下是一些重要的注解:

  • @Api: 将类标记为 Swagger 资源

  • @ApiModel: 为 Swagger 模型提供更多信息

  • @ApiModelProperty: 添加并操作模型属性的数据

  • @ApiOperation: 描述对特定路径的操作或 HTTP 方法

  • @ApiParam: 为操作参数添加额外的元数据

  • @ApiResponse: 描述操作的示例响应

  • @ApiResponses: 一个包装器,允许列出多个 ApiResponse 对象。

  • @Authorization: 声明在资源或操作上使用的授权方案

  • @AuthorizationScope: 描述 OAuth 2 授权范围

  • @ResponseHeader: 表示可以作为响应的一部分提供的头

Swagger 提供了一些 Swagger 定义注解,可以用来自定义一组服务的高级信息--联系人、许可和其他一般信息。以下列出了一些重要的注解:

  • @SwaggerDefinition: 要添加到生成的 Swagger 定义中的定义级属性

  • @Info: Swagger 定义的一般元数据

  • @Contact: 用于描述 Swagger 定义中要联系的人的属性

  • @License: 用于描述 Swagger 定义许可的属性

使用 Spring Security 保护 REST 服务

我们到目前为止创建的所有服务都是未受保护的。消费者不需要提供任何凭证来访问这些服务。然而,现实世界中的所有服务通常都是受保护的。

在本节中,我们将讨论两种认证 REST 服务的方法:

  • 基本认证

  • OAuth 2.0 认证

我们将使用 Spring Security 实现这两种类型的认证。

Spring Boot 使用 spring-boot-starter-security 提供了 Spring Security 的启动器。我们将从将 Spring Security 启动器添加到我们的 pom.xml 文件开始。

添加 Spring Security Starter

将以下依赖项添加到您的 pom.xml 文件中:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

Spring-boot-starter-security 依赖项引入了三个重要的 Spring Security 依赖项:

  • spring-security-config

  • spring-security-core

  • spring-security-web

基本认证

Spring-boot-starter-security 依赖项默认情况下也会自动为所有服务配置基本认证。

如果我们现在尝试访问任何服务,我们会得到 "访问被拒绝"

在以下代码片段中,以示例的形式展示了向 http://localhost:8080/users/Jack/todos 发送请求时的响应:

    {
      "timestamp": 1484120815039,
      "status": 401,
      "error": "Unauthorized",
      "message": "Full authentication is required to access this 
       resource",
       "path": "/users/Jack/todos"
    }

响应状态是 401 - 未授权

当使用基本认证保护资源时,我们需要发送用户 ID 和密码来认证我们的请求。由于我们没有配置用户 ID 和密码,Spring Boot 会自动配置默认的用户 ID 和密码。默认用户 ID 是 user。默认密码通常会在日志中打印出来。

以下代码片段展示了示例:

2017-01-11 13:11:58.696 INFO 3888 --- [restartedMain] b.a.s.AuthenticationManagerConfiguration :

Using default security password: 3fb5564a-ce53-4138-9911-8ade17b2f478

2017-01-11 13:11:58.771 INFO 3888 --- [restartedMain] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: Ant [pattern='/css/**'], []

在前面的代码片段中,下划线表示日志中打印的默认安全密码。

我们可以使用 Postman 发送带有基本认证的请求。以下截图显示了如何将基本认证详情与请求一起发送:

基本认证

如您所见,认证成功,我们得到了适当的响应。

我们可以在 application.properties 中配置我们选择的用户 ID 和密码,如下所示:

   security.user.name=user-name
   security.user.password=user-password

Spring Security 还提供了使用 LDAP、JDBC 或任何其他数据源和用户凭证进行认证的选项。

集成测试

我们之前为服务编写的集成测试将因凭证无效而开始失败。我们现在将更新集成测试以提供基本认证凭证:

    private TestRestTemplate template = new TestRestTemplate();
    HttpHeaders headers = createHeaders("user-name", "user-password");

    HttpHeaders createHeaders(String username, String password) {
      return new HttpHeaders() {
       {
         String auth = username + ":" + password;
         byte[] encodedAuth = Base64.getEncoder().encode
         (auth.getBytes(Charset.forName("US-ASCII")));
         String authHeader = "Basic " + new String(encodedAuth);
         set("Authorization", authHeader);
        }
      };
     }

    @Test
    public void retrieveTodos() throws Exception {
      String expected = "["
      + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" + ","
      + "{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";
      ResponseEntity<String> response = template.exchange(
      createUrl("/users/Jack/todos"), HttpMethod.GET,
      new HttpEntity<String>(null, headers),
      String.class);
      JSONAssert.assertEquals(expected, response.getBody(), false);
    }

以下是一些需要注意的重要事项:

createHeaders("user-name", "user-password"): 此方法创建Base64\. getEncoder().encode基本认证头

ResponseEntity<String> response = template.exchange(createUrl("/users/Jack/todos"), HttpMethod.GET, new HttpEntity<String>(null, headers), String.class): 关键变化是使用HttpEntity将我们之前创建的头部信息提供给 REST 模板

单元测试

我们不希望在我们的单元测试中使用安全设置。以下代码片段展示了我们如何禁用单元测试中的安全设置:

   @RunWith(SpringRunner.class)
   @WebMvcTest(value = TodoController.class, secure = false)
   public class TodoControllerTest {

关键部分是WebMvcTest注解上的secure = false参数。这将禁用单元测试中的 Spring Security。

OAuth 2 认证

OAuth 是一种协议,它提供了一系列流程,以便在一系列启用 Web 的应用程序和服务之间交换授权和认证信息。它允许第三方应用程序从服务(例如 Facebook、Twitter 或 GitHub)获取对用户信息的受限访问。

在我们深入细节之前,回顾一下通常与 OAuth 2 认证相关的术语会有所帮助。

让我们考虑一个例子。假设我们想将Todo API 暴露给互联网上的第三方应用程序。

以下是在典型的 OAuth 2 交换中的重要参与者:

  • 资源所有者:这是想要使用我们的 Todo API 的第三方应用程序的用户。它决定可以提供给第三方应用程序的我们 API 中可用信息量。

  • 资源服务器:这是托管 Todo API,我们想要保护的资源。

  • 客户端:这是想要消费我们 API 的第三方应用程序。

  • 授权服务器:这是提供 OAuth 服务的服务器。

高级流程

以下步骤展示了典型 OAuth 认证的高级别流程:

  1. 应用请求用户授权访问 API 资源。

  2. 当用户提供访问权限时,应用程序会收到一个授权许可。

  3. 应用程序向授权服务器提供用户授权许可和其自身的客户端凭证。

  4. 如果认证成功,授权服务器会响应一个访问令牌。

  5. 应用程序调用提供访问令牌进行认证的 API(资源服务器)。

  6. 如果访问令牌有效,资源服务器将返回资源的详细信息。

为我们的服务实现 OAuth 2 认证

OAuth 2 for Spring Security (spring-security-oauth2)是提供 OAuth 2 支持给 Spring Security 的模块。我们将将其作为依赖项添加到我们的pom.xml文件中:

    <dependency>
      <groupId>org.springframework.security.oauth</groupId>
      <artifactId>spring-security-oauth2</artifactId>
    </dependency>

设置授权和资源服务器:

注意

spring-security-oauth2 还未更新以包含 Spring Framework 5.x 和 Spring Boot 2.x 的更改(截至 2017 年 6 月)。我们将使用 Spring Boot 1.5.x 作为 OAuth 2 认证相关示例。代码示例在 GitHub 仓库中:github.com/PacktPublishing/Mastering-Spring-5.0

通常,授权服务器将与应用程序中公开 API 的服务器不同。为了简化问题,我们将使我们的当前 API 服务器同时作为资源服务器和授权服务器。

以下代码片段显示了如何使我们的应用程序能够作为资源和授权服务器:

   @EnableResourceServer
   @EnableAuthorizationServer
   @SpringBootApplication
   public class Application {

这里有一些需要注意的重要事项:

@EnableResourceServer:一个便利的注解,用于 OAuth 2 资源服务器,启用一个 Spring Security 过滤器,通过传入的 OAuth 2 令牌进行请求认证

@EnableAuthorizationServer:一个便利的注解,用于在当前应用程序上下文中启用一个授权服务器,包括;AuthorizationEndpoint 和;TokenEndpoint,该上下文必须是一个 DispatcherServlet 上下文

现在,我们可以在 application.properties 中配置访问详情,如下面的代码片段所示:

    security.user.name=user-name
    security.user.password=user-password
    security.oauth2.client.clientId: clientId
    security.oauth2.client.clientSecret: clientSecret
    security.oauth2.client.authorized-grant-types:     
    authorization_code,refresh_token,password
    security.oauth2.client.scope: openid

以下是一些重要的细节:

security.user.namesecurity.user.password 是资源所有者的认证详情,该资源所有者是第三方应用程序的最终用户

security.oauth2.client.clientIdsecurity.oauth2.client.clientSecret 是客户端的认证详情,该客户端是第三方应用程序(服务消费者)

执行 OAuth 请求

我们需要两步过程来访问 API:

  1. 获取访问令牌。

  2. 使用访问令牌执行请求。

获取访问令牌

要获取访问令牌,我们调用授权服务器(http://localhost:8080/oauth/token),在基本认证模式下提供客户端认证详情,并将用户凭证作为表单数据的一部分。以下截图显示了如何配置基本认证中的客户端认证详情:

获取访问令牌

以下截图显示了如何将用户认证详情作为 POST 参数的一部分进行配置:

获取访问令牌

我们使用 grant_type 作为密码,表示我们正在发送用户认证详情以获取访问令牌。当我们执行请求时,我们得到一个类似于以下代码片段的响应:

    {
      "access_token": "a633dd55-102f-4f53-bcbd-a857df54b821",
      "token_type": "bearer",
      "refresh_token": "d68d89ec-0a13-4224-a29b-e9056768c7f0",
      "expires_in": 43199,
      "scope": "openid"
    }

这里有一些重要的细节:

  • access_token:客户端应用程序可以使用访问令牌来验证进一步的 API 调用。然而,访问令牌将过期,通常在非常短的时间段内。

  • refresh_token:客户端应用程序可以使用 refresh_token 向认证服务器提交新的请求,以获取新的 access_token

使用访问令牌执行请求

一旦我们有了access_token,我们可以使用access_token来执行请求,如下面的截图所示:

使用访问令牌执行请求

如您在前面的截图中所见,我们在名为Authorization的请求头中提供了访问令牌。我们使用格式"Bearer {access_token}"的值。认证成功,我们得到了预期的资源详情。

集成测试

现在我们将更新我们的集成测试以提供 OAuth 2 凭证。以下测试突出了重要细节:

    @Test
    public void retrieveTodos() throws Exception {
      String expected = "["
      + "{id:1,user:Jack,desc:\"Learn Spring MVC\",done:false}" + ","
      +"{id:2,user:Jack,desc:\"Learn Struts\",done:false}" + "]";
      String uri = "/users/Jack/todos";
      ResourceOwnerPasswordResourceDetails resource = 
      new ResourceOwnerPasswordResourceDetails();
      resource.setUsername("user-name");
      resource.setPassword("user-password");
      resource.setAccessTokenUri(createUrl("/oauth/token"));
      resource.setClientId("clientId");
      resource.setClientSecret("clientSecret");
      resource.setGrantType("password");
      OAuth2RestTemplate oauthTemplate = new 
      OAuth2RestTemplate(resource,new 
      DefaultOAuth2ClientContext());
      ResponseEntity<String> response = 
      oauthTemplate.getForEntity(createUrl(uri), String.class);
     JSONAssert.assertEquals(expected, response.getBody(), false);
    }

以下是一些需要注意的重要事项:

  • ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails():我们使用用户凭证和客户端凭证设置ResourceOwnerPasswordResourceDetails

  • resource.setAccessTokenUri(createUrl("/oauth/token")):配置认证服务器的 URL

  • OAuth2RestTemplate oauthTemplate = new OAuth2RestTemplate(resource,new DefaultOAuth2ClientContext())OAuth2RestTemplateRestTemplate的扩展,支持 OAuth 2 协议

在本节中,我们探讨了如何在我们的资源中启用 OAuth 2 认证。

国际化

国际化i18n)是开发应用程序和服务的过程,以便它们可以针对世界各地的不同语言和文化进行定制。它也被称为本地化。国际化或本地化的目标是构建可以提供多种语言和格式的应用程序。

Spring Boot 内置了对国际化的支持。

让我们构建一个简单的服务,以了解我们如何在我们的 API 中构建国际化。

我们需要在我们的 Spring Boot 应用程序中添加一个LocaleResolver和一个消息源。以下代码片段应包含在Application.java中:

    @Bean
    public LocaleResolver localeResolver() {
      SessionLocaleResolver sessionLocaleResolver = 
      new SessionLocaleResolver();
      sessionLocaleResolver.setDefaultLocale(Locale.US);
      return sessionLocaleResolver;
    }

   @Bean
   public ResourceBundleMessageSource messageSource() {
     ResourceBundleMessageSource messageSource = 
     new ResourceBundleMessageSource();
     messageSource.setBasenames("messages");
     messageSource.setUseCodeAsDefaultMessage(true);
    return messageSource;
   }

以下是一些需要注意的重要事项:

  • sessionLocaleResolver.setDefaultLocale(Locale.US):我们正在设置默认区域为Locale.US

  • messageSource.setBasenames("messages"):我们将消息源的基本名称设置为messages。如果我们处于 fr 区域(法国),我们将使用message_fr.properties中的消息。如果message_fr.properties中没有找到消息,它将在默认的message.properties中搜索。

  • messageSource.setUseCodeAsDefaultMessage(true):如果找不到消息,则返回代码作为默认消息。

让我们在各自的文件中配置消息。让我们从messages属性开始。这个文件中的消息将作为默认值:

    welcome.message=Welcome in English

让我们也配置messages_fr.properties。这个文件中的消息将用于区域。如果这里没有消息,则将使用messages.properties中的默认值:

   welcome.message=Welcome in French

让我们创建一个服务,该服务使用"Accept-Language"头中指定的区域返回特定的消息:

    @GetMapping("/welcome-internationalized")
    public String msg(@RequestHeader(value = "Accept-Language", 
    required = false) Locale locale) {
      return messageSource.getMessage("welcome.message", null, 
      locale);
    }

这里有一些需要注意的事项:

@RequestHeader(value = "Accept-Language", required = false) Locale locale: 区域设置是从请求头 Accept-Language 中获取的。这不是必需的。如果没有指定区域设置,则使用默认区域设置。

messageSource.getMessage("welcome.message", null, locale): messageSource 自动注入到控制器中。我们根据给定的区域设置获取欢迎消息。

以下截图显示了在未指定默认 Accept-Language 时调用先前服务时的响应:

国际化

messages.properties 返回默认消息。

以下截图显示了在调用先前服务时带有 Accept-Language fr 的响应:

国际化

messages_fr.properties 返回的本地化消息。

在前面的示例中,我们定制了服务以根据请求中的区域设置返回本地化消息。类似的方法可以用来国际化组件中的所有服务。

缓存

从服务中缓存数据在提高应用程序的性能和可伸缩性方面起着至关重要的作用。在本节中,我们将探讨 Spring Boot 提供的实现选项。

Spring 提供了一个基于注解的缓存抽象。我们将从使用 Spring 缓存注解开始。稍后,我们将介绍 JSR-107 缓存注解,并将它们与 Spring 抽象进行比较。

Spring-boot-starter-cache

Spring Boot 为缓存提供了一个启动项目 spring-boot-starter-cache。将此添加到应用程序中会引入所有依赖项以启用 JSR-107 和 Spring 缓存注解。以下代码片段展示了 spring-boot-starter-cache 的依赖项细节。让我们将其添加到我们的 pom.xml 文件中:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

启用缓存

在我们开始使用缓存之前,我们需要在应用程序上启用缓存。以下代码片段展示了我们如何启用缓存:

    @EnableCaching
    @SpringBootApplication
    public class Application {

@EnableCaching 将在 Spring Boot 应用程序中启用缓存。

Spring Boot 自动配置了一个合适的 CacheManager 框架,作为相关缓存的提供者。我们稍后会看看 Spring Boot 如何决定 CacheManager 的细节。

缓存数据

现在我们已经启用了缓存,我们可以在想要缓存数据的方法上添加 @Cacheable 注解。以下代码片段展示了如何在 retrieveTodos 上启用缓存:

    @Cacheable("todos")
    public List<Todo> retrieveTodos(String user) {

在前面的示例中,特定用户的 todos 被缓存。在第一次调用特定用户的方法时,todos 将从服务中检索。在随后的对同一用户的调用中,数据将从缓存中返回。

Spring 还提供了条件缓存。在以下代码片段中,只有当指定的条件满足时才启用缓存:

    @Cacheable(cacheNames="todos", condition="#user.length < 10")
    public List<Todo> retrieveTodos(String user) {

Spring 还提供了额外的注解来从缓存中驱逐数据并添加一些自定义数据到缓存。以下列出了一些重要的注解:

  • @CachePut:用于显式地将数据添加到缓存中

  • @CacheEvict:用于从缓存中删除过时数据

  • @Caching:允许在同一个方法上使用多个嵌套的 @Cacheable@CachePut@CacheEvict 注解

JSR-107 缓存注解

JSR-107 旨在标准化缓存注解。以下列出了一些重要的 JSR-107 注解:

  • @CacheResult:类似于 @Cacheable

  • @CacheRemove:类似于 @CacheEvict,如果发生异常,@CacheRemove 支持条件删除

  • @CacheRemoveAll:类似于 @CacheEvict(allEntries=true);用于从缓存中删除所有条目

JSR-107 和 Spring 的缓存注解在提供的功能方面相当相似。两者都是不错的选择。我们稍微倾向于 JSR-107,因为它是一个标准。然而,请确保你不在同一个项目中使用两者。

自动检测顺序

当启用缓存时,Spring Boot 自动配置开始寻找缓存提供者。以下列表显示了 Spring Boot 搜索缓存提供者的顺序。列表按偏好递减的顺序排列:

  • JCache (JSR-107)(EhCache 3、Hazelcast、Infinispan 等)

  • EhCache 2.x

  • Hazelcast

  • Infinispan
Couchbase

  • Redis

  • Caffeine

  • Guava

  • 简单

摘要

Spring Boot 使基于 Spring 的应用程序的开发变得简单。它使我们能够非常快速地创建生产就绪的应用程序。

在本课中,我们介绍了如何将异常处理、缓存和国际化的功能添加到我们的应用程序中。我们讨论了使用 Swagger 记录 REST 服务的最佳实践。我们探讨了如何使用 Spring Security 保护我们的微服务的基础知识。

在下一课中,我们将把注意力转向 Spring Boot 的高级功能。我们将探讨如何在我们的 REST 服务上提供监控,学习如何将微服务部署到云端,以及了解如何在使用 Spring Boot 开发应用程序时提高生产力。

评估

  1. _________ 提供了一组可用于验证 Bean 的注解。

  2. 以下哪个是必须位于指定边界内的注解元素大小?

    1. @SizeOf

    2. @SizeBoundary

    3. @SizeTo

    4. @Size

  3. 判断对错:HATEOAS 是 REST 应用架构的关键特性之一。

  4. 以下哪个是一个简单的构建器类,用于使用 Swagger Spring MVC 框架配置生成 Swagger 文档?

    1. Docket

    2. Swagger

    3. REST

    4. QAuth

  5. 以下哪个是 OAuth 2 资源服务器的一个方便的注解,它启用了一个通过传入的 OAuth 2 令牌进行请求认证的 Spring Security 过滤器?

    1. @enableResourceServer

    2. @enablesResourcesServer

    3. @EnableResourceServer

    4. @EnableResourceServers

第三章。高级 Spring Boot 特性

在上一课中,我们通过异常处理、HATEOAS、缓存和国际化扩展了我们的微服务。在本课中,让我们将注意力转向将我们的服务部署到生产环境。为了能够将服务部署到生产环境,我们需要能够设置和创建配置、部署和监控服务的功能。

在本课中,我们将回答以下问题:

  • 如何外部化应用程序配置?

  • 如何使用配置文件来配置特定环境的值?

  • 如何将我们的应用程序部署到云中?

  • 什么是嵌入式服务器?如何使用 Tomcat、Jetty 和 Undertow?

  • Spring Boot Actuator 提供了哪些监控功能?

  • 如何使用 Spring Boot 成为更高效的开发者?

外部化配置

应用程序通常只构建一次(在 JAR 或 WAR 文件中),然后部署到多个环境中。以下图显示了应用程序可以部署的一些不同环境:

外部化配置

在上述每个环境中,应用程序通常具有以下特点:

  • 数据库连接

  • 多个服务的连接

  • 特定环境配置

将在不同环境之间发生变化的配置外部化到配置文件或数据库中是一种良好的实践。

Spring Boot 提供了一种灵活、标准化的外部化配置方法。

在本节中,我们将探讨以下内容:

  • 如何在服务内部使用application.properties中的属性?

  • 如何让类型安全的配置属性使应用程序配置变得轻而易举?

  • Spring Boot 为Spring 配置文件提供了哪些支持?

  • 如何在application.properties中配置属性?

在 Spring Boot 中,application.properties是默认的配置值来源文件。Spring Boot 可以从类路径上的任何位置获取application.properties文件。通常,application.properties位于src\main\resources,如下面的截图所示:

外部化配置

在第 2 课“扩展微服务”中,我们通过application.properties中的配置示例来查看自定义 Spring Security 的方法:

    security.basic.enabled=false
    management.security.enabled=false
    security.user.name=user-name
    security.user.password=user-password
    security.oauth2.client.clientId: clientId
    security.oauth2.client.clientSecret: clientSecret
    security.oauth2.client.authorized-grant-types:                
    authorization_code,refresh_token,password
    security.oauth2.client.scope: openid

与这些类似,所有其他 Spring Boot 启动器、模块和框架都可以通过application.properties中的配置进行自定义。在下一节中,我们将探讨 Spring Boot 为这些框架提供的某些配置选项。

通过application.properties自定义框架

在本节中,我们将讨论一些可以通过application.properties配置的重要事项。

注意

对于完整的列表,请参阅docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#common-application-properties

日志

可以配置的一些事项如下:

  • 日志配置文件的位置

  • 日志文件的位置

  • 日志级别

以下代码片段展示了几个示例:

# Location of the logging configuration file.
  logging.config=
# Log file name.
  logging.file=
# Configure Logging level. 
# Example `logging.level.org.springframework=TRACE`
  logging.level.*=

内嵌服务器配置

内嵌服务器是 Spring Boot 最重要的特性之一。可以通过应用程序属性配置的一些内嵌服务器特性包括:

  • 服务器端口

  • SSL 支持和配置

  • 访问日志配置

以下代码片段展示了可以通过应用程序属性配置的一些内嵌服务器特性:

# Path of the error controller.
server.error.path=/error 
# Server HTTP port
server.port=8080
# Enable SSL support.
server.ssl.enabled=
# Path to key store with SSL certificate
server.ssl.key-store=
# Key Store Password
server.ssl.key-store-password=
# Key Store Provider
server.ssl.key-store-provider=
# Key Store Type
server.ssl.key-store-type=
# Should we enable access log of Tomcat?
server.tomcat.accesslog.enabled=false
# Maximum number of connections that server can accept
server.tomcat.max-connections=

Spring MVC

Spring MVC 可以通过application.properties进行广泛的配置。以下是一些重要的配置:

# Date format to use. For instance `dd/MM/yyyy`.
 spring.mvc.date-format=
# Locale to use.
 spring.mvc.locale=
# Define how the locale should be resolved.
 spring.mvc.locale-resolver=accept-header
# Should "NoHandlerFoundException" be thrown if no Handler is found?
 spring.mvc.throw-exception-if-no-handler-found=false
# Spring MVC view prefix. Used by view resolver.
 spring.mvc.view.prefix=
# Spring MVC view suffix. Used by view resolver.
 spring.mvc.view.suffix=

Spring Starter Security

Spring Security 可以通过application.properties进行广泛的配置。以下示例展示了与 Spring Security 相关的一些重要配置选项:

# Set true to Enable basic authentication
 security.basic.enabled=true
# Provide a Comma-separated list of uris you would want to secure
 security.basic.path=/**
# Provide a Comma-separated list of paths you don't want to secure
 security.ignored=
# Name of the default user configured by spring security
 security.user.name=user
# Password of the default user configured by spring security. 
 security.user.password=
# Roles granted to default user
 security.user.role=USER

数据源、JDBC 和 JPA

数据源、JDBC 和 JPA 也可以通过application.properties进行广泛的配置。以下是一些重要的选项:

# Fully qualified name of the JDBC driver. 
 spring.datasource.driver-class-name=
# Populate the database using 'data.sql'.
 spring.datasource.initialize=true
# JNDI location of the datasource.
 spring.datasource.jndi-name=
# Name of the datasource.
 spring.datasource.name=testdb
# Login password of the database.
 spring.datasource.password=
# Schema (DDL) script resource references.
 spring.datasource.schema=
# Db User to use to execute DDL scripts
 spring.datasource.schema-username=
# Db password to execute DDL scripts
 spring.datasource.schema-password=
# JDBC url of the database.
 spring.datasource.url=
# JPA - Initialize the schema on startup.
 spring.jpa.generate-ddl=false
# Use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
 spring.jpa.hibernate.use-new-id-generator-mappings=
# Enable logging of SQL statements.
 spring.jpa.show-sql=false

其他配置选项

可以通过application.properties配置的一些其他事项如下:

  • 配置文件

  • HTTP 消息转换器(Jackson/JSON)

  • 事务管理

  • 国际化

以下示例展示了可以通过应用程序属性配置的一些配置选项:

# Comma-separated list (or list if using YAML) of active profiles.
 spring.profiles.active=
# HTTP message conversion. jackson or gson
 spring.http.converters.preferred-json-mapper=jackson
# JACKSON Date format string. Example `yyyy-MM-dd HH:mm:ss`.
 spring.jackson.date-format=
# Default transaction timeout in seconds.
 spring.transaction.default-timeout=
# Perform the rollback on commit failures.
 spring.transaction.rollback-on-commit-failure=
# Internationalization : Comma-separated list of basenames
 spring.messages.basename=messages
# Cache expiration for resource bundles, in sec. -1 will cache for ever
 spring.messages.cache-seconds=-1

Application.Properties 中的自定义属性

到目前为止,我们已经探讨了使用 Spring Boot 提供的预构建属性来配置各种框架。在本节中,我们将探讨创建我们应用程序特定的配置,这些配置也可以在application.properties中进行配置。

让我们考虑一个例子。我们希望能够与外部服务交互。我们希望能够外部化此服务 URL 的配置。

以下示例展示了我们如何在application.properties中配置外部服务:

   somedataservice.url=http://abc.service.com/something

我们想在数据服务中使用somedataservice.url属性的值。以下代码片段展示了如何在示例数据服务中实现这一点。

    @Component
    public class SomeDataService {
      @Value("${somedataservice.url}")
      private String url;
      public String retrieveSomeData() {
        // Logic using the url and getting the data
       return "data from service";
      }
    }

有两点需要注意:

  • @Component public class SomeDataService: 数据服务 bean 由于@Component注解而被 Spring 管理。

  • @Value("${somedataservice.url}"): somedataservice.url的值将被自动注入到url变量中。该url值可以在 bean 的方法中使用。

配置属性 - 类型安全的配置管理

虽然@Value注解提供了动态配置,但它也有一些缺点:

  • 如果我们想在服务中使用三个属性值,我们需要使用@Value注解三次来自动注入它们。

  • @Value 注解和消息的键将在应用程序中分散。如果我们想在应用程序中找到可配置值的列表,我们必须在应用程序中搜索@Value注解。

Spring Boot 通过强类型的 ConfigurationProperties 功能提供了一种更好的应用程序配置方法。这允许我们做以下事情:

  • 在预定义的 Bean 结构中包含所有属性

  • 这个 Bean 将作为所有应用程序属性的集中存储。

  • 配置 Bean 可以在需要应用程序配置的地方进行自动注入。

以下是一个示例配置 Bean:

    @Component
    @ConfigurationProperties("application")
    public class ApplicationConfiguration {
      private boolean enableSwitchForService1;
      private String service1Url;
      private int service1Timeout;
      public boolean isEnableSwitchForService1() {
        return enableSwitchForService1;
      }
     public void setEnableSwitchForService1
     (boolean enableSwitchForService1) {
        this.enableSwitchForService1 = enableSwitchForService1;
      }
     public String getService1Url() {
       return service1Url;
     }
     public void setService1Url(String service1Url) {
       this.service1Url = service1Url;
     }
     public int getService1Timeout() {
       return service1Timeout;
     }
     public void setService1Timeout(int service1Timeout) {
       this.service1Timeout = service1Timeout;
    }
  }

以下是一些需要注意的重要事项:

  • @ConfigurationProperties("application") 是用于外部化配置的注解。我们可以将此注解添加到任何类中,以绑定到外部属性。双引号中的值--application--在将外部配置绑定到此 Bean 时用作前缀。

  • 我们在 Bean 中定义了多个可配置的值。

  • 由于绑定是通过 Java Bean 属性描述符发生的,因此需要 getters 和 setters。

下面的代码片段显示了如何在application.properties中定义这些属性的值:

    application.enableSwitchForService1=true
    application.service1Url=http://abc-dev.service.com/somethingelse
    application.service1Timeout=250

以下是一些需要注意的重要事项:

  • application: 前缀是在定义配置 Bean 时作为 @ConfigurationProperties("application") 的一部分定义的

  • 值是通过将前缀附加到属性名称来定义的

我们可以通过将 ApplicationConfiguration 自动注入到 Bean 中来在其他 Bean 中使用配置属性:

    @Component
    public class SomeOtherDataService {
      @Autowired
      private ApplicationConfiguration configuration;
      public String retrieveSomeData() {
        // Logic using the url and getting the data
        System.out.println(configuration.getService1Timeout());
        System.out.println(configuration.getService1Url());
        System.out.println(configuration.isEnableSwitchForService1());
        return "data from service";
      }
    }

以下是一些需要注意的重要事项:

  • @Autowired private ApplicationConfiguration configuration: ApplicationConfiguration 被自动注入到 SomeOtherDataService

  • configuration.getService1Timeout(), configuration.getService1Url(), configuration.isEnableSwitchForService1(): 可以通过配置 Bean 上的 getter 方法在 Bean 方法中访问这些值

默认情况下,将外部配置值绑定到配置属性 Bean 的任何失败都会导致服务器启动失败。这防止了由于在生产环境中运行配置错误的应用程序而产生的问题。

让我们使用错误的配置服务超时来查看会发生什么:

    application.service1Timeout=SOME_MISCONFIGURATION

应用程序将无法启动并出现错误。

***************************
 APPLICATION FAILED TO START
 ***************************
Description:
Binding to target com.mastering.spring.springboot.configuration.ApplicationConfiguration@79d3473e failed:

Property: application.service1Timeout
Value: SOME_MISCONFIGURATION
Reason: Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'service1Timeout'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [int]

Action:
Update your application's configuration

配置文件

到目前为止,我们探讨了如何将应用程序配置外部化到属性文件中,即application.properties。我们想要实现的是在不同的环境中,相同的属性可以有不同的值。

配置文件提供了一种在不同环境中提供不同配置的方法。

下面的代码片段显示了如何在application.properties中配置活动配置文件:

    spring.profiles.active=dev

一旦配置了活动配置文件,你就可以在application-{profile-name}.properties中定义特定于该配置文件的属性。对于dev配置文件,属性文件的名称将是application-dev.properties。以下示例显示了application-dev.properties中的配置:

    application.enableSwitchForService1=true
    application.service1Url=http://abc-dev.service.com/somethingelse
    application.service1Timeout=250

如果活动配置文件是dev,则application-dev.properties中的值将覆盖application.properties中的默认配置。

我们可以有多个环境的配置,如下所示:

配置文件

基于配置文件的 Bean 配置

配置文件也可以用来定义不同环境中的不同 bean 或不同的 bean 配置。所有标记了@Component@Configuration的类也可以通过额外的@Profile注解来指定启用 bean 或配置的配置文件。

让我们考虑一个例子。一个应用程序需要在不同的环境中启用不同的缓存。在dev环境中,它使用一个非常简单的缓存。在生产环境中,我们希望使用分布式缓存。这可以通过配置文件来实现。

以下 bean 显示了在dev环境中启用的配置:

    @Profile("dev")
    @Configuration
    public class DevSpecificConfiguration {
      @Bean
      public String cache() {
        return "Dev Cache Configuration";
      }
    }

以下 bean 显示了在生产环境中启用的配置:

    @Profile("prod")
    @Configuration
    public class ProdSpecificConfiguration {
      @Bean
      public String cache() {
        return "Production Cache Configuration - Distributed Cache";
      }
   }

根据配置的活动配置文件,选择相应的配置。注意,在这个例子中,我们实际上并没有配置分布式缓存。我们返回一个简单的字符串来展示配置文件可以用来实现这类变化。

应用配置值的其他选项

到目前为止,我们采取的方法是使用application.propertiesapplication-{profile-name}.properties中的键值对来配置应用程序属性。

Spring Boot 提供了许多其他配置应用程序属性的方法。

列出的是提供应用程序配置的一些重要方式:

  • 命令行参数

  • 创建名为SPRING_APPLICATION_JSON的系统属性并包含 JSON 配置

  • ServletConfig init参数

  • ServletContext init参数

  • Java 系统属性(System.getProperties()

  • 操作系统环境变量

  • .jar文件外的特定配置文件的应用程序属性,位于应用程序的类路径中(application-{profile}.properties

  • 打包在您的.jar文件内的特定配置文件的应用程序属性(application-{profile}.properties和 YAML 变体)

  • .jar文件外的应用程序属性

  • 打包在.jar文件内的应用程序属性

更多信息可以在 Spring Boot 文档中找到:docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config

列表顶部的方案比列表底部的方案具有更高的优先级。例如,如果在启动应用程序时提供了名为 spring.profiles.active 的命令行参数,它将覆盖通过 application.properties 提供的任何配置,因为命令行参数具有更高的优先级。

这为确定您希望在不同环境中如何配置应用程序提供了极大的灵活性。

YAML 配置

Spring Boot 也支持使用 YAML 来配置你的属性。

YAMLYAML Ain't Markup Language 的缩写。它是一种人类可读的结构化格式。YAML 通常用于配置文件。

要了解 YAML 的基本语法,请查看以下示例(application.yaml)。这显示了我们的应用程序配置如何在 YAML 中指定。

spring:
   profiles:
      active: prod
security:
   basic:
      enabled: false
   user:
      name=user-name
      password=user-password
oauth2:
   client:
      clientId: clientId
      clientSecret: clientSecret
      authorized-grant-types: authorization_code,refresh_token,password
      scope: openid
application:
   enableSwitchForService1: true
   service1Url: http://abc-dev.service.com/somethingelse
   service1Timeout: 250

如您所见,YAML 配置比 application.properties 更易于阅读,因为它允许更好地对属性进行分组。

YAML 的另一个优点是它允许你在单个配置文件中指定多个配置文件的配置。以下片段显示了示例:

application:
  service1Url: http://service.default.com
---	
spring:
  profiles: dev
  application:
    service1Url: http://service.dev.com
---
spring:
   profiles: prod
   application:
    service1Url: http://service.prod.com

在此示例中,http://service.dev.com 将在 dev 配置文件中使用,而 http://service.prod.comprod 配置文件中使用。在所有其他配置文件中,将使用 http://service.default.com 作为服务 URL。

嵌入式服务器

Spring Boot 带入的一个重要概念之一是嵌入式服务器。

让我们先了解传统 Java 网络应用程序部署与这种称为嵌入式服务器的新概念之间的区别。

传统上,使用 Java 网络应用程序,我们构建 Web 应用程序存档WAR)或 企业应用程序存档EAR),并将它们部署到服务器上。在我们可以在服务器上部署 WAR 之前,需要在服务器上安装一个 web 服务器或应用程序服务器。应用程序服务器将位于服务器上安装的 Java 实例之上。因此,在我们部署应用程序之前,需要在机器上安装 Java 和一个应用程序(或 web 服务器)。以下图显示了 Linux 中的示例安装:

嵌入式服务器

Spring Boot 引入了嵌入式服务器的概念,其中 web 服务器是应用程序可部署部分——JAR。要使用嵌入式服务器部署应用程序,只要服务器上安装了 Java 就足够了。以下图显示了示例安装:

嵌入式服务器

当我们使用 Spring Boot 构建任何应用程序时,默认是构建一个 JAR。使用 spring-boot-starter-web,默认嵌入式服务器是 Tomcat。

当我们使用 spring-boot-starter-web 时,可以在 Maven 依赖项部分看到一些与 Tomcat 相关的依赖项。这些依赖项将作为应用程序部署包的一部分包含:

嵌入式服务器

要部署应用程序,我们需要构建一个 JAR。我们可以使用以下命令构建 JAR:

mvn clean install

以下截图显示了创建的 JAR 的结构。

BOOT-INF\classes 包含所有与应用程序相关的类文件(来自src\main\java)以及来自src\main\resources的应用程序属性:

嵌入式服务器

以下截图显示了BOOT-INF\lib中的一些库:

嵌入式服务器

BOOT-INF\lib 包含应用程序的所有 JAR 依赖项。其中包含三个特定的 Tomcat JAR。这三个 JAR 使得当应用程序作为 Java 应用程序运行时可以启动嵌入式 Tomcat 服务。正因为如此,一个 Java 安装就足够在服务器上部署此应用程序。

切换到 Jetty 和 Undertow

以下截图显示了切换到使用 Jetty 嵌入式服务器所需的更改。

切换到 Jetty 和 Undertow

我们需要做的只是排除spring-boot-starter-web中的 Tomcat 启动器依赖项,并包含spring-boot-starter-jetty中的依赖项。

你现在可以在 Maven 依赖项部分看到许多 Jetty 依赖项。以下截图显示了几个与 Jetty 相关的依赖项:

切换到 Jetty 和 Undertow

切换到 Undertow 同样简单。使用spring-boot-starter-undertow代替spring-boot-starter-jetty

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
   </dependency>

构建 WAR 文件

Spring Boot 还提供了构建传统 WAR 文件而不是使用 JAR 的选项。

首先,我们需要将pom.xml中的打包方式改为WAR

    <packaging>war</packaging>

我们希望防止将 tomcat 服务器作为依赖项嵌入到 WAR 文件中。我们可以通过修改嵌入式服务器(以下示例中的 Tomcat)的依赖项,使其作用域为提供。以下代码片段显示了确切细节:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
   </dependency>

当你构建 WAR 文件时,不会包含 Tomcat 依赖项。我们可以使用这个 WAR 文件部署到应用程序服务器,如 WebSphere 或 Weblogic,或者一个网络服务器,如 Tomcat。

开发者工具

Spring Boot 提供了可以提升开发 Spring Boot 应用程序体验的工具。其中之一就是 Spring Boot 开发者工具。

要使用 Spring Boot 开发者工具,我们需要包含一个依赖项:

    <dependencies>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-devtools</artifactId>
       <optional>true</optional>
     </dependency>
   </dependencies>

默认情况下,Spring Boot 开发者工具禁用了视图模板和静态文件的缓存。这使得开发者能够在他们做出更改后立即看到更改。

另一个重要特性是当类路径中的任何文件发生变化时自动重启。因此,应用程序在以下场景下会自动重启:

  • 当我们对控制器或服务类进行修改时

  • 当我们对属性文件进行修改时

Spring Boot 开发者工具的优点如下:

  • 开发者不需要每次都停止和启动应用程序。一旦有变化,应用程序就会自动重启。

  • Spring Boot 开发者工具中的重启功能是智能的。它只重新加载正在积极开发的类。它不会重新加载第三方 JAR(使用两个不同的类加载器)。因此,当应用程序中的某些内容发生变化时,重启速度比冷启动应用程序要快得多。

Live Reload

Spring Boot 开发者工具的另一个有用功能是 实时重新加载。您可以从 livereload.com/extensions/ 下载适用于您浏览器的特定插件。

您可以通过点击浏览器中的按钮来启用实时重新加载。以下截图显示了 Safari 浏览器中的按钮。它在地址栏旁边的右上角。

实时重新加载

如果在浏览器中显示的页面或服务上有代码更改,它们将自动用新内容刷新。不再需要点击刷新按钮了!

Spring Boot Actuator

当应用程序部署到生产环境时:

  • 我们希望立即知道是否有某个服务宕机或运行非常缓慢

  • 我们希望立即知道是否有任何服务器没有足够的空闲空间或内存

这被称为 应用程序监控

Spring Boot Actuator 提供了许多生产就绪的监控功能。

我们将通过添加一个简单的依赖项来添加 Spring Boot Actuator:

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
     </dependency>
   </dependencies>

一旦将 Actuator 添加到应用程序中,它就会启用许多端点。当我们启动应用程序时,我们会看到许多新增的映射。以下截图显示了启动日志中这些新映射的摘录:

Spring Boot Actuator

Actuator 公开了许多端点。Actuator 端点(http://localhost:8080/application)充当所有其他端点的发现。以下截图显示了从 Postman 执行请求时的响应:

Spring Boot Actuator

HAL 浏览器

许多这些端点公开了大量数据。为了更好地可视化信息,我们将在应用程序中添加一个 HAL 浏览器

    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-rest-hal-browser</artifactId>
    </dependency>

Spring Boot Actuator 在从 Spring Boot 应用程序和环境捕获的所有数据周围公开了 REST API。HAL 浏览器使 Spring Boot Actuator API 的可视化表示成为可能:

HAL 浏览器

当我们在浏览器中启动 http://localhost:8080/application 时,我们可以看到 actuator 公开的所有 URL。

HAL 浏览器

让我们通过 HAL 浏览器浏览 actuator 作为不同端点的一部分公开的所有信息。

配置属性

configprops 端点提供了有关可以通过应用程序属性配置的配置选项的信息。它基本上是一个所有 @ConfigurationProperties 的汇总列表。以下截图显示了 HAL 浏览器中的 configprops

配置属性

为了说明一个已知示例,以下服务响应的部分显示了 Spring MVC 可用的配置选项:

"spring.mvc-  org.springframework.boot.autoconfigure.web.WebMvcProperties": {
   "prefix": "spring.mvc",
   "properties": {
                   "dateFormat": null,
                   "servlet": {
                     "loadOnStartup": -1
                  },
   "staticPathPattern": "/**",
   "dispatchOptionsRequest": true,
   "dispatchTraceRequest": false,
   "locale": null,
   "ignoreDefaultModelOnRedirect": true,
   "logResolvedException": true,
   "async": {
              "requestTimeout": null
            },
   "messageCodesResolverFormat": null,
   "mediaTypes": {},
   "view": {
             "prefix": null,
             "suffix": null
           },
   "localeResolver": "ACCEPT_HEADER",
   "throwExceptionIfNoHandlerFound": false
    }
 }

注意

为了为 Spring MVC 提供配置,我们在属性中结合了前缀和路径。例如,要配置loadOnStartup,我们使用名为spring.mvc.servlet.loadOnStartup的属性。

环境细节

环境env)端点提供了有关操作系统、JVM 安装、类路径、系统环境变量以及配置在各种应用程序属性文件中的值的详细信息。以下截图显示了 HAL 浏览器中的环境端点:

环境细节

下面展示了从/application/env服务响应中提取的内容。它显示了几个系统细节以及应用程序配置的详细信息:

"systemEnvironment": {
    "JAVA_MAIN_CLASS_13377": "com.mastering.spring.springboot.Application",
    "PATH": "/usr/bin:/bin:/usr/sbin:/sbin",
    "SHELL": "/bin/bash",
    "JAVA_STARTED_ON_FIRST_THREAD_13019": "1",
    "APP_ICON_13041": "../Resources/Eclipse.icns",
    "USER": "rangaraokaranam",
    "TMPDIR": "/var/folders/y_/x4jdvdkx7w94q5qsh745gzz00000gn/T/",
    "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.IcESePQCLV/Listeners",
    "XPC_FLAGS": "0x0",
    "JAVA_STARTED_ON_FIRST_THREAD_13041": "1",
    "APP_ICON_11624": "../Resources/Eclipse.icns",
    "LOGNAME": "rangaraokaranam",
    "XPC_SERVICE_NAME": "0",
    "HOME": "/Users/rangaraokaranam"
  },
  "applicationConfig: [classpath:/application-prod.properties]": {
    "application.service1Timeout": "250",
    "application.service1Url": "http://abc-    prod.service.com/somethingelse",
    "application.enableSwitchForService1": "false"
  },

健康

健康服务提供了有关磁盘空间和应用程序状态的信息。以下截图显示了从 HAL 浏览器执行的服务:

健康

映射

映射端点提供了有关从应用程序公开的不同服务端点的信息:

  • URI

  • 请求方法

  • 实例化 bean

  • 暴露服务的控制器方法

映射提供了所有@RequestMapping路径的汇总列表。以下是从/application/mappings端点响应中提取的内容:

"{[/welcome-internationalized],methods=[GET]}": {
   "bean": "requestMappingHandlerMapping",
   "method": "public java.lang.String 
    com.mastering.spring.springboot.controller.
    BasicController.msg(java.uti l.Locale)"
 },
 "{[/welcome],methods=[GET]}": {
    "bean": "requestMappingHandlerMapping",
    "method": "public java.lang.String 
     com.mastering.spring.springboot.controller.
     BasicController.welcome()"
 },
 "{[/welcome-with-object],methods=[GET]}": {
     "bean": "requestMappingHandlerMapping",
     "method": "public com.mastering.spring.springboot.
      bean.WelcomeBeancom.mastering.spring.springboot.
      controller.BasicController.welcomeWithObject()"
 },
 "{[/welcome-with-parameter/name/{name}],methods=[GET]}": {
      "bean": "requestMappingHandlerMapping",
      "method": "public 
       com.mastering.spring.springboot.bean.WelcomeBean   
       com.mastering.spring.springboot.controller.
       BasicController.welcomeWithParameter(java.lang.String)"
 },
 "{[/users/{name}/todos],methods=[POST]}": {
       "bean": "requestMappingHandlerMapping",
       "method": "org.springframework.http.ResponseEntity<?>    
        com.mastering.spring.springboot.controller.
        TodoController.add(java.lang.String,com.mastering.spring.
        springboot.bean.Todo)"
  },
 "{[/users/{name}/todos],methods=[GET]}": {
        "bean": "requestMappingHandlerMapping",
        "method": "public java.util.List<com.mastering.spring.
         springboot.bean.Todo> 
         com.mastering.spring.springboot.controller.
         TodoController.retrieveTodos(java.lang.String)"
 },
 "{[/users/{name}/todos/{id}],methods=[GET]}": {
        "bean": "requestMappingHandlerMapping",
        "method": "public 
         org.springframework.hateoas.Resource<com.mastering.
         spring.springboot.bean.Todo>  
         com.mastering.spring.springboot.controller.
         TodoController.retrieveTodo(java.lang.String,int)"
 },

实例化 bean

实例化 bean 端点提供了加载到 Spring 上下文中的 bean 的详细信息。这在调试与 Spring 上下文相关的任何问题时非常有用。

An extract from the response of the /application/beans endpoint is shown below:
  {
     "bean": "basicController",
     "aliases": [],
     "scope": "singleton",
     "type": "com.mastering.spring.springboot.
      controller.BasicController",
     "resource": "file [/in28Minutes/Workspaces/
      SpringTutorial/mastering-spring-lesson-5-6-  
      7/target/classes/com/mastering/spring/springboot/
      controller/BasicController.class]",
      "dependencies": [
                     "messageSource"
                    ]
   },
   {
      "bean": "todoController",
      "aliases": [],
      "scope": "singleton",
      "type": "com.mastering.spring.springboot.
       controller.TodoController",
       "resource": "file [/in28Minutes/Workspaces/SpringTutorial/
       mastering-spring-lesson-5-6-
       7/target/classes/com/mastering/spring/
       springboot/controller/TodoController.class]",
       "dependencies": [
                      "todoService"
                     ]
    }

它显示了两个 bean 的详细信息:basicControllertodoController。你可以看到以下所有 bean 的详细信息:

  • 实例化 bean 的名称及其别名

  • 豆类的范围

  • 实例化 bean 的类型

  • 创建此 bean 的类的确切位置

  • 实例化 bean 的依赖项

指标

指标端点显示了以下重要指标:

  • 服务器--空闲内存、处理器、运行时间等

  • JVM--有关堆、线程、垃圾回收、会话等的详细信息

  • 应用程序服务提供的响应

下面展示了/application/metrics端点响应的提取内容:

{
 "mem": 481449,
 "mem.free": 178878,
 "processors": 4,
 "instance.uptime": 1853761,
 "uptime": 1863728,
 "systemload.average": 2.3349609375,
 "heap.committed": 413696,
 "heap.init": 65536,
 "heap.used": 234817,
 "heap": 932352,
 "nonheap.committed": 69248,
 "nonheap.init": 2496,
 "nonheap.used": 67754,
 "nonheap": 0,
 "threads.peak": 23,
 "threads.daemon": 21,
 "threads.totalStarted": 30,
 "threads": 23,
 "classes": 8077,
 "classes.loaded": 8078,
 "classes.unloaded": 1,
 "gc.ps_scavenge.count": 15,
 "gc.ps_scavenge.time": 242,
 "gc.ps_marksweep.count": 3,
 "gc.ps_marksweep.time": 543,
 "httpsessions.max": -1,
 "httpsessions.active": 0,
 "gauge.response.actuator": 8,
 "gauge.response.mappings": 12,
 "gauge.response.beans": 83,
 "gauge.response.health": 14,
 "gauge.response.root": 9,
 "gauge.response.heapdump": 4694,
 "gauge.response.env": 6,
 "gauge.response.profile": 12,
 "gauge.response.browser.star-star": 10,
 "gauge.response.actuator.root": 2,
 "gauge.response.configprops": 272,
 "gauge.response.actuator.star-star": 13,
 "counter.status.200.profile": 1,
 "counter.status.200.actuator": 8,
 "counter.status.200.mappings": 1,
 "counter.status.200.root": 5,
 "counter.status.200.configprops": 1,
 "counter.status.404.actuator.star-star": 3,
 "counter.status.200.heapdump": 1,
 "counter.status.200.health": 1,
 "counter.status.304.browser.star-star": 132,
 "counter.status.302.actuator.root": 4,
 "counter.status.200.browser.star-star": 37,
 "counter.status.200.env": 2,
 "counter.status.302.root": 5,
 "counter.status.200.beans": 1,
 "counter.status.200.actuator.star-star": 210,
 "counter.status.302.actuator": 1
 }

自动配置

自动配置是 Spring Boot 最重要的功能之一。自动配置端点(/application/autoconfig)公开了与自动配置相关的详细信息。它显示了正匹配和负匹配,并详细说明了为什么特定的自动配置成功或失败。

以下是从响应中提取的一些正匹配项:

"positiveMatches": {
  "AuditAutoConfiguration#auditListener": [
   {
     "condition": "OnBeanCondition",
     "message": "@ConditionalOnMissingBean (types:     
      org.springframework.boot.actuate.audit.
      listener.AbstractAuditListener; SearchStrategy: all) did not find 
      any beans"
   }
 ],
 "AuditAutoConfiguration#authenticationAuditListener": [
 {
   "condition": "OnClassCondition",
   "message": "@ConditionalOnClass found required class
   'org.springframework.security.authentication.
   event.AbstractAuthenticationEvent'"
 },

以下是从响应中提取的一些负匹配项:

"negativeMatches": {
  "CacheStatisticsAutoConfiguration.
   CaffeineCacheStatisticsProviderConfiguration": [
 {
   "condition": "OnClassCondition",
   "message": "@ConditionalOnClass did not find required class  
   'com.github.benmanes.caffeine.cache.Caffeine'"
 }
 ],
   "CacheStatisticsAutoConfiguration.
   EhCacheCacheStatisticsProviderConfiguration": [
 {
   "condition": "OnClassCondition",
   "message": "@ConditionalOnClass did not find required classes
   'net.sf.ehcache.Ehcache',   
   'net.sf.ehcache.statistics.StatisticsGateway'"
 }
 ],

所有这些详细信息对于调试自动配置非常有用。

调试

在调试问题时,有三个 actuator 端点非常有用:

  • /application/heapdump:提供堆转储

  • /application/trace:提供由应用程序服务的最后几个请求的跟踪信息

  • /application/dump: 提供线程转储

将应用程序部署到云

Spring Boot 对大多数流行的云平台即服务PaaS)提供商有很好的支持。

其中一些流行的如下所示:

  • Cloud Foundry

  • Heroku

  • OpenShift

  • 亚马逊网络服务AWS

在本节中,我们将专注于将我们的应用程序部署到 Cloud Foundry。

Cloud Foundry

Cloud Foundry 的 Java 构建包对 Spring Boot 有很好的支持。我们可以部署基于 JAR 的独立应用程序以及传统的 Java EE WAR 应用程序。

Cloud Foundry 提供了一个 Maven 插件来部署应用程序:

<build>
   <plugins>
      <plugin>
         <groupId>org.cloudfoundry</groupId>
         <artifactId>cf-maven-plugin</artifactId>
         <version>1.1.2</version>
      </plugin>
   </plugins>
</build>

在我们能够部署我们的应用程序之前,我们需要配置应用程序的目标和部署应用程序的空间。

以下是涉及的步骤:

  • 我们需要在account.run.pivotal.io/sign-up创建一个 Pivotal Cloud Foundry 账户。

  • 一旦我们有了账户,我们可以在run.pivotal.io登录以创建一个组织和空间。准备好 org 和 space 的详细信息,因为我们需要它们来部署应用程序。

我们可以使用orgspace的配置来更新插件:

<build>
   <plugins>
      <plugin>
         <groupId>org.cloudfoundry</groupId>
         <artifactId>cf-maven-plugin</artifactId>
         <version>1.1.2</version>
         <configuration>
            <target>http://api.run.pivotal.io</target>
            <org>in28minutes</org>
            <space>development</space>
            <memory>512</memory>
            <env>
               <ENV-VAR-NAME>prod</ENV-VAR-NAME>
            </env>
         </configuration>
      </plugin>
   </plugins>
</build>

我们需要使用命令提示符或终端上的 Maven 插件登录到 Cloud Foundry:

mvn cf:login -Dcf.username=<<YOUR-USER-ID>> -Dcf.password=<<YOUR-PASSWORD>>

如果一切顺利,您将看到如下所示的消息:

 [INFO] ------------------------------------------------------------------
 [INFO] Building Your First Spring Boot Example 0.0.1-SNAPSHOT
 [INFO] -----------------------------------------------------------------
 [INFO]
 [INFO] --- cf-maven-plugin:1.1.2:login (default-cli) @ springboot-for-beginners-example ---
 [INFO] Authentication successful
 [INFO] -----------------------------------------------------------------
 [INFO] BUILD SUCCESS
 [INFO] -----------------------------------------------------------------
 [INFO] Total time: 14.897 s
 [INFO] Finished at: 2017-02-05T16:49:52+05:30
 [INFO] Final Memory: 22M/101M
 [INFO] -----------------------------------------------------------------

一旦您能够登录,您可以将应用程序推送到 Cloud Foundry:

mvn cf:push

一旦我们执行了命令,Maven 将编译、运行测试、构建应用程序 JAR 或 WAR 文件,然后将其部署到云:

 [INFO] Building jar: /in28Minutes/Workspaces/SpringTutorial/springboot-for-beginners-example-rest-service/target/springboot-for-beginners-example-0.0.1-SNAPSHOT.jar
 [INFO]
 [INFO] --- spring-boot-maven-plugin:1.4.0.RELEASE:repackage (default) @ springboot-for-beginners-example ---
 [INFO]
 [INFO] <<< cf-maven-plugin:1.1.2:push (default-cli) < package @ springboot-for-beginners-example <<<
 [INFO]
 [INFO] --- cf-maven-plugin:1.1.2:push (default-cli) @ springboot-for-beginners-example ---
 [INFO] Creating application 'springboot-for-beginners-example'
 [INFO] Uploading '/in28Minutes/Workspaces/SpringTutorial/springboot-for-beginners-example-rest-service/target/springboot-for-beginners-example-0.0.1-SNAPSHOT.jar'
 [INFO] Starting application
 [INFO] Checking status of application 'springboot-for-beginners-example'
 [INFO] 1 of 1 instances running (1 running)
 [INFO] Application 'springboot-for-beginners-example' is available at 'http://springboot-for-beginners-example.cfapps.io'
 [INFO] ----------------------------------------------------------------- [INFO] BUILD SUCCESS
 [INFO] ----------------------------------------------------------------- [INFO] Total time: 02:21 min
 [INFO] Finished at: 2017-02-05T16:54:55+05:30
 [INFO] Final Memory: 29M/102M
 [INFO] -----------------------------------------------------------------

一旦应用程序在云上运行,我们可以使用日志中的 URL 来启动应用程序:springboot-for-beginners-example.cfapps.io

注意

您可以在docs.run.pivotal.io/buildpacks/java/build-tool-int.html#maven找到更多关于 Cloud Foundry 的 Java 构建包的信息。

摘要

Spring Boot 使基于 Spring 的应用程序开发变得简单。它使我们能够非常快速地创建生产就绪的应用程序。

在本课中,我们了解了 Spring Boot 提供的不同外部配置选项。我们查看嵌入式服务器并将测试应用程序部署到 PaaS 云平台--Cloud Foundry。我们探讨了如何在生产中使用 Spring Boot Actuator 来监控我们的应用程序。最后,我们查看使开发者更高效的功能--Spring Boot 开发者工具和实时重载。

通过这种方式,我们已经到达了这本书的结尾。希望您有一个顺利的旅程,并且从使用 Spring Boot 构建微服务中获得了大量的知识。

我祝愿您未来的项目一切顺利。继续学习和探索!

评估

  1. The ________ 端点提供有关操作系统、JVM 安装、类路径、系统环境变量以及配置在各种应用程序属性文件中的值的详细信息。

  2. 以下哪个是由云提供给 Cloud Foundry 的?

    1. 软件即服务

    2. 平台即服务

    3. 基础设施即服务

    4. 以上所有选项

  3. 判断以下哪个是正确的:Spring MVC 可以通过application.properties进行广泛的配置。

  4. 以下哪个 actuator 端点在调试时提供了应用程序最近服务的几个请求的跟踪。

    1. /applications/trace

    2. /application/tracing

    3. /app/trace

    4. /apps/tracing

  5. _________ 使得基于 Spring 的应用开发变得简单,因为它能让你快速创建生产就绪的应用程序。

第四章. 评估答案

第 1 课:使用 Spring Boot 构建微服务

问题编号 答案
1 SpringApplication
2 4
3 True
4 1
5 False

第 2 课:扩展微服务

问题编号 答案
1 Bean 验证 API
2 4
3 False
4 1
5 3

第 3 课:高级 Spring Boot 功能

问题编号 答案
1 环境(env)
2 2
3 True
4 1
5 Spring Boot
posted @ 2025-09-12 13:55  绝不原创的飞龙  阅读(48)  评论(0)    收藏  举报