博客

【注】本文译自: https://www.tutorialspoint.com/spring_boot/spring_boot_application_properties.htm

    应用属性能够支持在不同环境中工作。本文将带你学习如何在 Spring Boot 应用中配置特定的属性。

命令行属性

    Spring Boot 应用将命令行属性转换为 Spring Boot 环境属性。命令行属性优先于其他属性源。Spring Boot 默认使用 8080 端口号启动 Tomcat。让我们学习如何使用命令行属性改变它。

第1步: 创建可执行 JAR 文件,使用命令 java –jar <JARFILE> 运行。

第2步: 如下面截屏的命令所示,利用命令行属性改变 Spring Boot 应用的端口号:

注意: 你可以使用分隔符号 – 提供多个应用属性。

属性文件

    属性文件的作用在于,在不同环境中运行的应用使用单个属性文件配置多个属性。Spring Boot 中属性被配置在 application.properties 文件中,这个文件要在 classpath 路径中。   application.properties 文件位于 src/main/resources 目录。

以下代码是 application.properties 文件的示例:

server.port = 9090
spring.application.name = demoservice

   要注意上面的代码指定 Spring Boot 应用 demoservice 启动的端口号为 9090。

YAML 文件

   Spring Boot 支持基于 YAML 的属性配置来运行应用。代之以 application.properties,我们可以使用 application.yml 文件。这个 YAML 文件也应当在 classpath 路径下。application.yml 文件示例如下:

spring:
   application:
      name: demoservice
   server:
port: 9090

外化属性

   不用在 classpath 路径下的属性文件,我们也可以在不同的位置和路径下使用。在运行 JAR 文件时,也可以指定属性文件路径,如下所示:

-Dspring.config.location = C:\application.properties

使用 @Value 注解

   @Value 注解用于在 Java 代码中读取环境或应用属性。读取属性的语法如下所示:

@Value("${property_key_name}")

   下面的例子展示了如何在 Java 变量中使用 @Value 注解读取 spring.application.name 的属性值。

@Value("${spring.application.name}")

   看下面的代码,更好理解:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {
   @Value("${spring.application.name}")
   private String name;
   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
   @RequestMapping(value = "/")
   public String name() {
      return name;
   }
}

注意:如果在运行应用中没有找到指定的属性,Spring Boot 会抛出非法参数异常,如:Could not resolve placeholder ‘spring.application.name’ in value “${spring.application.name}”。   要解决占位符问题,可以设置属性的缺省值,如下所示:

@Value("${property_key_name:default_value}")

@Value("${spring.application.name:demoservice}")

Spring Boot 活动配置

   Spring Boot 支持基于 Spring 活动配置的不同属性。例如,我们可以为开发和生产环境下运行 Spring Boot 应用配置不同的属性文件。

在 application.properties 中的 spring 活动配置

   我们来理解一下如何 application.properties 中的 Spring 活动配置。缺省情况下,Spring Boot 应用会使用 application. properties 来运行。如果你想使用基于属性的配置以如下所示来保存每个配置:

application.properties

server.port = 8080
spring.application.name = demoservice

application-dev.properties

server.port = 9090
spring.application.name = demoservice

application-prod.properties

server.port = 4431
spring.application.name = demoservice

   运行 JAR 文件时,需要基于每个属性文件来指定 spring 活动配置。缺省情况下,Spring Boot 应用使用 application.properties 文件。设置 spring active 配置的命令如下所示:

   你可以在控制台日志中看到活动配置名,如下所示:

2017-11-26 08:13:16.322 INFO 14028 --- [
main] com.tutorialspoint.demo.DemoApplication :
The following profiles are active: dev

    现在 Tomcat 已经在 9090 (http) 端口启动了,如下所示:

2017-11-26 08:13:20.185 INFO 14028 --- [
main] s.b.c.e.t.TomcatEmbeddedServletContainer :
Tomcat started on port(s): 9090 (http)

    如下所示可以指定生产环境的活动配置:

   在控制台日志中可以看到活动配置名,如下所示:

2017-11-26 08:13:16.322 INFO 14028 --- [
main] com.tutorialspoint.demo.DemoApplication :
The following profiles are active: prod

   现在 Tomcat 已经在 4431 (http) 端口启动了,如下所示:

2017-11-26 08:13:20.185 INFO 14028 --- [
main] s.b.c.e.t.TomcatEmbeddedServletContainer :
Tomcat started on port(s): 4431 (http)

针对 application.yml 的 Spring 活动配置 

   接下来,我们看下针对 application.yml 如何设置 Spring 活动配置。我们可以在单个 application.yml 文件中设置 Spring 活动配置。不需要象 application.properties 那样使用不同的的属性文件。   下面是一个 application.yml 文件示例。注意分隔符 (—) 用于隔离在 application.yml 文件中的不同配置。

spring:
   application:
      name: demoservice
server:
   port: 8080

---
spring:
   profiles: dev
   application:
      name: demoservice
server:
   port: 9090

---
spring:
   profiles: prod
   application:
      name: demoservice
server:
   port: 4431

   如下所示设置开发环境的活动配置:

   如下所示,你可以在控制台日志中看到活动的配置:

2017-11-26 08:41:37.202 INFO 14104 --- [
main] com.tutorialspoint.demo.DemoApplication :
The following profiles are active: dev

   现在 Tomcat 已经在 9090 (http) 端口启动了,如下所示:

2017-11-26 08:41:46.650 INFO 14104 --- [
main] s.b.c.e.t.TomcatEmbeddedServletContainer :
Tomcat started on port(s): 9090 (http)

   在控制台日志中可以看到活动配置名,如下所示:

   如下所示可以指定生产环境的活动配置:

2017-11-26 08:43:10.743 INFO 13400 --- [
main] com.tutorialspoint.demo.DemoApplication :
The following profiles are active: prod

   现在 Tomcat 已经在 4431 (http) 端口启动了,如下所示:

2017-11-26 08:43:14.473 INFO 13400 --- [
main] s.b.c.e.t.TomcatEmbeddedServletContainer :
Tomcat started on port(s): 4431 (http)

【注】本文译自:  https://www.tutorialspoint.com/spring_boot/spring_boot_logging.htm

    Spring Boot 使用 Apache Commons logging 作为内部日志。Spring Boot 缺省配置提供对于 Java Util Logging, Log4j2 和 Logback 的支持。这样,我们可以配置控制台或者文件日志。

    如果使用 Spring Boot 启动器,Logback 会提供很好的日志支持。此外,Logback 对 Common Logging、Util Logging、Log4J 和 SLF4J 的支持也不错。

Log 格式

    缺省 Spring Boot 的日志格式如下图所示:

    提供了以下信息:

  • Date 和 Time 给出日志的日期和时间
  • Log level 显示 INFO, ERROR 或 WARN
  • Process ID
  • 用 — 作为分隔符
  • Thread name 在 方括号 [] 内
  • Logger Name 显示源类名
  • 日志消息

控制台日志输出

    默认日志消息会打印到控制台窗口。缺少情况下,“INFO”, “ERROR” 和 “WARN” 日志消息会在日志文件中打印。    如果要允许 debug 级别的日志,用命令行启动应用时加上 debug 标识,如下所示:

java –jar demo.jar --debug

    也可以在 application.properties 中加入调试模式,如下所示:

debug = true

文件日志输出

    默认情况下,所有的日志都会不打印到控制台窗口而不是在文件中。如果你要将日志打印到文件中,需要在 application.properties 文件中设置 logging.file 或 logging.path 属性。

    可以如下所示设置日志文件路径,注意日志文件名为 spring.log。

logging.path = /var/tmp/

注意:文件在达到 10 MB 时将自动转储。

日志级别

    Spring Boot 支持所有的日志级别,如 “TRACE”, “DEBUG”, “INFO”, “WARN”, “ERROR”, “FATAL”, “OFF”。可以在 application.properties 文件中定义根日志,如下所示:

logging.level.root = WARN

注意: Logback 不支持 “FATAL” 级别的日志,它会被映射为 “ERROR” 级别的日志。

配置 Logback

    Logback 支持基于 XML 的配置来处理 Spring Boot 日志配置。日志配置细节在 logback.xml 配置文件中。logback.xml 文件应当位于 classpath 路径下。    可以在 Logback.xml 文件中配置根级别日志,如下所示:

<?xml version = "1.0" encoding = "UTF-8"?>
<configuration>
   <root level = "INFO">
   </root>
</configuration>

    可以在 Logback.xml 文件中配置 控制台 appender,如下所示:

<?xml version = "1.0" encoding = "UTF-8"?>
<configuration>
   <appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender"></appender>
   <root level = "INFO">
      <appender-ref ref = "STDOUT"/>
   </root>
</configuration>

    可以在 Logback.xml 配置文件 appender,如下所示。注意需要在 file appender 中指定日志文件路径。

<?xml version = "1.0" encoding = "UTF-8"?>
<configuration>
   <appender name = "FILE" class = "ch.qos.logback.core.FileAppender">
      <File>/var/tmp/mylog.log</File>
   </appender>   
   <root level = "INFO">
      <appender-ref ref = "FILE"/>
   </root>
</configuration>

    可以在 logback.xml 文件中定义日志范式,也可以定义在控制台或者文件中的日志范式集,如下所示:

<pattern>[%d{yyyy-MM-dd'T'HH:mm:ss.sss'Z'}] [%C] [%t] [%L] [%-5p] %m%n</pattern>

    完整的 logback.xml 文件如下,需要放在 class path 路径下:

<?xml version = "1.0" encoding = "UTF-8"?>
<configuration>
   <appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>[%d{yyyy-MM-dd'T'HH:mm:ss.sss'Z'}] [%C] [%t] [%L] [%-5p] %m%n</pattern>
      </encoder>
   </appender>
   
   <appender name = "FILE" class = "ch.qos.logback.core.FileAppender">
      <File>/var/tmp/mylog.log</File>
      <encoder>
         <pattern>[%d{yyyy-MM-dd'T'HH:mm:ss.sss'Z'}] [%C] [%t] [%L] [%-5p] %m%n</pattern>
      </encoder>
   </appender>
   
   <root level = "INFO">
      <appender-ref ref = "FILE"/>
      <appender-ref ref = "STDOUT"/>
   </root>
</configuration>

    下面的代码展示了如何在主类文件中加上 slf4j logger:

package com.tutorialspoint.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
   private static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);
   
   public static void main(String[] args) {
      logger.info("this is a info message");
      logger.warn("this is a warn message");
      logger.error("this is a error message");
      SpringApplication.run(DemoApplication.class, args);
   }
}

    控制台窗口输出如下所示:

    日志文件输出如下所示:

【注】本文译自:  https://www.tutorialspoint.com/spring_boot/spring_boot_building_restful_web_services.htm

    Spring Boot 提供了构建企业应用中 RESTful Web 服务的极佳支持。本文为你详解如何使用 Spring Boot 构建 RESTful web 服务。

注意:要构建 RESTful Web 服务,我们需要在构建配置文件中加上 Spring Boot Starter Web 依赖。

    对于 Maven 用户,使用以下的代码在 pom.xml 文件中加入依赖:

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

    对于 Gradle 用户,使用以下的代码在 build.gradle 文件中加入依赖:

compile('org.springframework.boot:spring-boot-starter-web')

    完整的构建配置文件 Maven build – pom.xml 如下:

<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
   
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>
   
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.8.RELEASE</version>
      <relativePath/>
   </parent>
   
   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>
   
   <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>
   </dependencies>
   
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
   
</project>

    完整的构建配置文件 Gradle Build – build.gradle 如下:

buildscript {
   ext {
      springBootVersion = '1.5.8.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}
dependencies {
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

    在继续构建 RESTful web 服务前,建议你先要熟悉下面的注解:

Rest Controller

   @RestController 注解用于定义 RESTful web 服务。它提供 JSON、XML 和自定义响应。语法如下所示:

@RestController
public class ProductServiceController {
}

Request Mapping

    @RequestMapping 注解用于定义请求 URI 以访问 REST 端点。我们可以定义 Request 方法来消费 produce 对象。默认的请求方法是 GET:

@RequestMapping(value = "/products")
public ResponseEntity<Object> getProducts() { }

Request Body

   @RequestBody 注解用于定义请求体内容类型。

public ResponseEntity<Object> createProduct(@RequestBody Product product) {
}

Path Variable

   @PathVariable 注解被用于定义自定义或动态的请求 URI,Path variable 被放在请求 URI 中的大括号内,如下所示:

public ResponseEntity<Object> updateProduct(@PathVariable("id") String id) {
}

Request Parameter

   @RequestParam 注解被用于从请求 URL 中读取请求参数。缺少情况下这是一个必需的参数,也可以为请求参数设置默认值,如下所示:

public ResponseEntity<Object> getProduct(
   @RequestParam(value = "name", required = false, defaultValue = "honey") String name) {
}

GET API

     默认的 HTTP 请求方法是 GET。这个方法不需要任何请求体。你可以通过发送请求参数和路径变量来定义自定义或动态 URL。

    下面的示例代码定义了 HTTP GET 请求方法。在这个例子吧,我们使用 HashMap 来在存储 Product。注意我们使用了 POJO 类来存储产品。

     在这里,请求 URI 是 /products,它会从 HashMap 仓储中返回产品列表。下面的控制器类文件包含了 GET 方法的 REST 端点:

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   static {
      Product honey = new Product();
      honey.setId("1");
      honey.setName("Honey");
      productRepo.put(honey.getId(), honey);
      
      Product almond = new Product();
      almond.setId("2");
      almond.setName("Almond");
      productRepo.put(almond.getId(), almond);
   }
   @RequestMapping(value = "/products")
   public ResponseEntity<Object> getProduct() {
      return new ResponseEntity<>(productRepo.values(), HttpStatus.OK);
   }
}

POST API

   HTTP POST 请求用于创建资源。这个方法包含请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。

   下面的示例代码定义了 HTTP POST 请求方法。在这个例子中,我们使用 HashMap 来存储 Product,这里产品是一个 POJO 类。

   这里,请求 URI 是 /products,在产品被存入 HashMap 仓储后,它会返回字符串。

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products", method = RequestMethod.POST)
   public ResponseEntity<Object> createProduct(@RequestBody Product product) {
      productRepo.put(product.getId(), product);
      return new ResponseEntity<>("Product is created successfully", HttpStatus.CREATED);
   }
}

PUT API

    HTTP PUT 请求用于更新已有的资源。这个方法包含请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。

    下面的例子展示了如何定义 HTTP PUT 请求方法。在这个例子中,我们使用 HashMap 更新现存的产品。此处,产品是一个 POJO 类。

    这里,请求 URI 是 /products/{id},在产品被存入 HashMap 仓储后,它会返回字符串。注意我们使用路径变量 {id} 定义需要更新的产品 ID:

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
   public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) {
      productRepo.remove(id);
      product.setId(id);
      productRepo.put(id, product);
      return new ResponseEntity<>("Product is updated successsfully", HttpStatus.OK);
   }   
}

DELETE API

   HTTP Delete 请求用于删除存在的资源。这个方法不包含任何请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。

   下面的例子展示如何定义 HTTP DELETE 请求方法。这个例子中,我们使用 HashMap 来移除现存的产品,用 POJO 来表示。

   请求 URI 是 /products/{id} 在产品被从 HashMap 仓储中删除后,它会返回字符串。 我们使用路径变量 {id} 来定义要被删除的产品 ID。

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE)
   public ResponseEntity<Object> delete(@PathVariable("id") String id) {
      productRepo.remove(id);
      return new ResponseEntity<>("Product is deleted successsfully", HttpStatus.OK);
   }
}

   下面给出完整的源代码:

Spring Boot 主应用类 – DemoApplication.java

package com.tutorialspoint.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

POJO 类 – Product.java

package com.tutorialspoint.demo.model;

public class Product {
   private String id;
   private String name;

   public String getId() {
      return id;
   }
   public void setId(String id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
}

Rest Controller 类 – ProductServiceController.java

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   static {
      Product honey = new Product();
      honey.setId("1");
      honey.setName("Honey");
      productRepo.put(honey.getId(), honey);
      
      Product almond = new Product();
      almond.setId("2");
      almond.setName("Almond");
      productRepo.put(almond.getId(), almond);
   }
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE)
   public ResponseEntity<Object> delete(@PathVariable("id") String id) {
      productRepo.remove(id);
      return new ResponseEntity<>("Product is deleted successsfully", HttpStatus.OK);
   }
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
   public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) {
      productRepo.remove(id);
      product.setId(id);
      productRepo.put(id, product);
      return new ResponseEntity<>("Product is updated successsfully", HttpStatus.OK);
   }
   
   @RequestMapping(value = "/products", method = RequestMethod.POST)
   public ResponseEntity<Object> createProduct(@RequestBody Product product) {
      productRepo.put(product.getId(), product);
      return new ResponseEntity<>("Product is created successfully", HttpStatus.CREATED);
   }
   
   @RequestMapping(value = "/products")
   public ResponseEntity<Object> getProduct() {
      return new ResponseEntity<>(productRepo.values(), HttpStatus.OK);
   }
}

   你可以创建可执行的 JAR 文件,使用以下的 Maven 或 Gradle 命令来运行 spring boot 应用:   Maven命令如下:

mvn clean install

   “构建成功“后,可以在 target 文件夹下发现 JAR 文件。

   Gradle命令如下:

gradle clean build

    “构建成功“后,可以在 build/libs 文件夹下发现 JAR 文件。

    可以使用以下命令来运行 JAR 文件:

    以 Tomcat 端口号 8080 启动应用,如下所示:

    以 Tomcat 端口号 8080 启动应用,如下所示:

    在 POSTMAN 应用中点击下面的 URL 可以看到相应的输出:GET API URL 为: http://localhost:8080/products

POST API URL 为: http://localhost:8080/products

PUT API URL 为: http://localhost:8080/products/3

DELETE API URL 为: http://localhost:8080/products/3

【注】本文译自:https://www.tutorialspoint.com/spring_boot/spring_boot_exception_handling.htm

    对于企业应用而言,在 API 中处理好异常和错误是至关重要的。本文将带你学习如果在 Spring Boot 中处理异常。    在学习异常处理前让我们先来理解下面的注解:

Controller Advice

    @ControllerAdvice 注解用于处理全局异常。

Exception Handler

    @ExceptionHandler 注解是用于处理指定的异常并向客户端发送一个自定义的响应。    可以使用下面的代码来创建 @ControllerAdvice 类以处理全局异常:

package com.tutorialspoint.demo.exception;

import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
   public class ProductExceptionController {
}

    定义一个继承 RuntimeException 的类。

package com.tutorialspoint.demo.exception;

public class ProductNotfoundException extends RuntimeException {
   private static final long serialVersionUID = 1L;
}

    如下所示,可以定义 @ExceptionHandler 方法来处理异常。这个方法应当被用于编写 Controller Advice 类文件。

@ExceptionHandler(value = ProductNotfoundException.class)

public ResponseEntity<Object> exception(ProductNotfoundException exception) {
}

    现在,使用以下代码来抛出来自 API 的异常:

@RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> updateProduct() {
   throw new ProductNotfoundException();
}

    完整的异常处理代码如下所示。在这个例子中,我们使用 PUT API 来更新产品。当更新产品时,如果找不到产品,返回的响应消息是 “Product not found”。注意 ProductNotFoundException 异常类应当继承 RuntimeException

package com.tutorialspoint.demo.exception;
public class ProductNotfoundException extends RuntimeException {
   private static final long serialVersionUID = 1L;
}

    Controller Advice 类处理全局异常,如下所示。我们可以在这个类文件中定义任何 Exception Handler 方法。

package com.tutorialspoint.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ProductExceptionController {
   @ExceptionHandler(value = ProductNotfoundException.class)
   public ResponseEntity<Object> exception(ProductNotfoundException exception) {
      return new ResponseEntity<>("Product not found", HttpStatus.NOT_FOUND);
   }
}

    下面的 Product Service API controller 文件更新产品。如果产品不存在,就抛出 ProductNotFoundException 类。

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.tutorialspoint.demo.exception.ProductNotfoundException;
import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();
   static {
      Product honey = new Product();
      honey.setId("1");
      honey.setName("Honey");
      productRepo.put(honey.getId(), honey);
      
      Product almond = new Product();
      almond.setId("2");
      almond.setName("Almond");
      productRepo.put(almond.getId(), almond);
   }
   
   @RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
   public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) {
      if(!productRepo.containsKey(id))throw new ProductNotfoundException();
      productRepo.remove(id);
      product.setId(id);
      productRepo.put(id, product);
      return new ResponseEntity<>("Product is updated successfully", HttpStatus.OK);
   }
}

    主 Spring Boot 应用类文件如下:

package com.tutorialspoint.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

    产品 POJO 类 如下:

package com.tutorialspoint.demo.model;
public class Product {
   private String id;
   private String name;

   public String getId() {
      return id;
   }
   public void setId(String id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
}

    Maven build – pom.xml 代码如下所示:

<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
   
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.8.RELEASE</version>
      <relativePath/>
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <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>
   </dependencies>

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

    Gradle Build – build.gradle 代码如下:

buildscript {
   ext {
      springBootVersion = '1.5.8.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}
dependencies {
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

    你可以使用 Maven 或 Gradle 命令构建 JAR 并运行 Spring Boot 应用:

    Maven 命令如下:

mvn clean install

    “BUILD SUCCESS” 之后,你可以在 target 目录下找到 JAR 文件。

    Gradle 可以使用如下命令:

gradle clean build

    “BUILD SUCCESSFUL” 后,你可以在 build/libs 目录下找到 JAR 文件。

    你可以使用下面的命令运行 JAR 文件。

java –jar <JARFILE>

    在 Tomcat 8080 端口启动应用,如下所示:

    现在,在 POSTMAN 应用中单击下面的 URL,你可以看到如下输出:    更新 URL: http://localhost:8080/products/3

【注】本文译自: https://www.tutorialspoint.com/spring_boot/spring_boot_interceptor.htm

        在以下两种情况下,可以在 Spring Boot 中使用拦截器来执行操作:

  • 发送请求到控制器之前
  • 发送响应到客户端之前

       譬如,可以使用拦截器在发送请求到控制器之前添加请求头,并在发送响应到客户端之前添加响应头。       要用拦截器,你需要创建 @Component 类,并且要实现 HandlerInterceptor 接口。       下面是使用拦截器要知道的三个方法:

  • preHandle() 方法:用于在发送请求到控制器之前执行操作。这个方法应当返回 true 以响应客户端。
  • postHandle() 方法:用于在发送响应到客户端之前执行操作。
  • afterCompletion() 方法: 用于在请求和响应完成之后执行操作。

       观察以下代码以加深理解:

@Component
public class ProductServiceInterceptor implements HandlerInterceptor {
   @Override
   public boolean preHandle(
      HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      
      return true;
   }
   @Override
   public void postHandle(
      HttpServletRequest request, HttpServletResponse response, Object handler,
      ModelAndView modelAndView) throws Exception {}
   
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
      Object handler, Exception exception) throws Exception {}
}

       还要通过 WebMvcConfigurerAdapter 用 InterceptorRegistry 注册这个拦截器:

@Component
public class ProductServiceInterceptorAppConfig extends WebMvcConfigurerAdapter {
   @Autowired
   ProductServiceInterceptor productServiceInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(productServiceInterceptor);
   }
}

       在下面给出的示例中,我们将要使用后面给出的 GET products API:

       Interceptor 类文件 ProductServiceInterceptor.java 如下所示:

package com.tutorialspoint.demo.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class ProductServiceInterceptor implements HandlerInterceptor {
   @Override
   public boolean preHandle
      (HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
      
      System.out.println("Pre Handle method is Calling");
      return true;
   }
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response,
      Object handler, ModelAndView modelAndView) throws Exception {
      
      System.out.println("Post Handle method is Calling");
   }
   @Override
   public void afterCompletion
      (HttpServletRequest request, HttpServletResponse response, Object
      handler, Exception exception) throws Exception {
      
      System.out.println("Request and Response is completed");
   }
}

        Application Configuration 类文件把拦截器注册到 Interceptor Registry中,ProductServiceInterceptorAppConfig.java 文件如下:

package com.tutorialspoint.demo.interceptor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Component
public class ProductServiceInterceptorAppConfig extends WebMvcConfigurerAdapter {
   @Autowired
   ProductServiceInterceptor productServiceInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(productServiceInterceptor);
   }
}

      Controller 类文件 ProductServiceController.java 如下:

package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.tutorialspoint.demo.exception.ProductNotfoundException;
import com.tutorialspoint.demo.model.Product;

@RestController
public class ProductServiceController {
   private static Map<String, Product> productRepo = new HashMap<>();   
   static {      
      Product honey = new Product();
      honey.setId("1");
      honey.setName("Honey");
      productRepo.put(honey.getId(), honey);      
      Product almond = new Product();
      almond.setId("2");
      almond.setName("Almond");
      productRepo.put(almond.getId(), almond);      
   }
   @RequestMapping(value = "/products")
   public ResponseEntity<Object> getProduct() {
      return new ResponseEntity<>(productRepo.values(), HttpStatus.OK);
   }
}

       POJO 类文件 Product.java 如下:

package com.tutorialspoint.demo.model;

public class Product {
   private String id;
   private String name;

   public String getId() {
      return id;
   }
   public void setId(String id) {
      this.id = id;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
}

       主 Spring Boot 应用类文件 DemoApplication.java 如下所示:

package com.tutorialspoint.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

       Maven build – pom.xml 文件在此:

<?xml version = "1.0" encoding = "UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "
   http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0
   http://maven.apache.org/xsd/maven-4.0.0.xsd">
   
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.tutorialspoint</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.8.RELEASE</version>
      <relativePath/>
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <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>
   </dependencies>

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

      Gradle Build build.gradle 文件在此:

buildscript {
   ext {
      springBootVersion = '1.5.8.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
group = 'com.tutorialspoint'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}
dependencies {
   compile('org.springframework.boot:spring-boot-starter-web')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}

      你可以使用下面的 Maven 或 Gradle 命令创建可执行的 JAR 文件,运行 Spring Boot 应用:       对于 Maven,使用以下命令:

mvn clean install

       在 “BUILD SUCCESS” 之后,你可以在 target 目录下找到 JAR 文件:

      对于 Gradle,使用以下命令:

gradle clean build

       在 “BUILD SUCCESSFUL” 之后,你可以在 build/libs 目录下找到 JAR 文件:

       可以使用以下命令运行 JAR 文件:

java –jar <JARFILE>

     此刻,应用已经在 Tomcat 8080 端口启动了,如下所示:

       在 POSTMAN 应用中单击以下 URL,你可以看到下面的结果:

GET API: http://localhost:8080/products

       在控制台窗口,你可以看到加在拦截器中的 System.out.println 打印出来的结果,截屏如下: