Spring Boot 2 REST APIs异常处理

x33g5p2x  于2022-10-06 转载在 Spring  
字(11.1k)|赞(0)|评价(0)|浏览(491)

在这篇文章中,我们将学习如何为使用Spring Boot开发的RESTful Web服务处理异常。
我们可以通过使用@ExceptionHandler@ControllerAdvice注解来处理REST API中的异常,与我们在基于SpringMVC的Web应用中处理异常的方式相同。你可以返回ResponseEntity,并附上适当的HTTP状态代码和异常细节,而不是渲染一个视图。

默认情况下,Spring Boot提供了一个/error映射,以合理的方式处理所有错误,它被注册为Servlet容器中的 "全局 "错误页面。在客户端,它会产生一个JSON响应,包含错误的细节、HTTP状态和异常消息。
与其简单地抛出一个带有HTTP状态代码的异常,不如提供更多关于这个问题的细节,如错误代码、消息、原因等。在这个例子中,我们定义了一个用@ControllerAdvice注解的类,以定制JSON文档,以返回特定的控制器和/或异常类型。

让我们使用Spring Boot 2+、JPA、Hibernate 5+、MySQL数据库,为Employeeresource开发一个CRUD REST APIs,我们将研究这些REST服务的异常处理。

1. 我们要建立什么?

我们将为Employee资源开发一个简单的Spring Boot RESTful CRUD APIs,我们将为这些RESTful服务实现异常(错误)处理。

2. 使用的工具和技术

  • Spring Boot - 2.0.4.RELEASE
  • JDK - 1.8或更高版本
  • Spring Framework - 5.0.8 RELEASE
  • Hibernate - 5.2.17.Final
  • Maven - 3.2以上
  • MySQL
  • IDE - Eclipse 或 Spring Tool Suite (STS)

3. 创建和导入一个项目

有很多方法可以创建Spring Boot应用程序。最简单的方法是在http://start.spring.io/使用Spring Initializr,它是一个在线Spring Boot应用程序生成器。

4. pom.xml文件

参考下面的pom.xmlf,供你参考。

<?xmlversion="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>net.guides.springboot2</groupId>
    <artifactId>springboot2-jpa-crud-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springboot2-jpa-crud-example</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </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-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </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>

下一步是我们将创建JPA实体 - Employee.java

5. 创建JPA实体 - Employee.java

package net.guides.springboot2.springboot2jpacrudexample.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "employees")
public class Employee {

    private long id;
    private String firstName;
    private String lastName;
    private String emailId;
 
    public Employee() {
  
    }
 
    public Employee(String firstName, String lastName, String emailId) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.emailId = emailId;
    }
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
 
    @Column(name = "first_name", nullable = false)
       public String getFirstName() {
           return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    @Column(name = "last_name", nullable = false)
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    @Column(name = "email_address", nullable = false)
    public String getEmailId() {
        return emailId;
    }
    public void setEmailId(String emailId) {
        this.emailId = emailId;
    }

    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", emailId=" + emailId
        + "]";
    }
}

接下来,我们将创建Spring Data JPA资源库 - EmployeeRepository.java

6. 创建Spring JPA存储库 - EmployeeRepository.java

package net.guides.springboot2.springboot2jpacrudexample.model.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.guides.springboot2.springboot2jpacrudexample.model.Employee;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{

}

一旦实体和资源库到位,我们将创建Spring Rest Controller - EmployeeController.java

7. 创建Spring Rest Controller - EmployeeController.java

package net.guides.springboot2.springboot2jpacrudexample.model.controller;

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

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import net.guides.springboot2.springboot2jpacrudexample.model.Employee;
import net.guides.springboot2.springboot2jpacrudexample.model.exception.ResourceNotFoundException;
import net.guides.springboot2.springboot2jpacrudexample.model.repository.EmployeeRepository;

@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
    @Autowired
    private EmployeeRepository employeeRepository;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @GetMapping("/employees/{id}")
    public ResponseEntity<Employee> getEmployeeById(@PathVariable(value = "id") Long employeeId)
        throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));
        return ResponseEntity.ok().body(employee);
    }

    @PostMapping("/employees")
    public Employee createEmployee(@Valid @RequestBody Employee employee) {
        return employeeRepository.save(employee);
    }

    @PutMapping("/employees/{id}")
    public ResponseEntity<Employee> updateEmployee(@PathVariable(value = "id") Long employeeId,
         @Valid @RequestBody Employee employeeDetails) throws ResourceNotFoundException {
         Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

        employee.setEmailId(employeeDetails.getEmailId());
        employee.setLastName(employeeDetails.getLastName());
        employee.setFirstName(employeeDetails.getFirstName());
        final Employee updatedEmployee = employeeRepository.save(employee);
        return ResponseEntity.ok(updatedEmployee);
    }

    @DeleteMapping("/employees/{id}")
    public Map<String, Boolean> deleteEmployee(@PathVariable(value = "id") Long employeeId)
        throws ResourceNotFoundException {
        Employee employee = employeeRepository.findById(employeeId)
        .orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + employeeId));

       employeeRepository.delete(employee);
       Map<String, Boolean> response = new HashMap<>();
       response.put("deleted", Boolean.TRUE);
       return response;
    }
}

到目前为止,我们已经为Employee资源开发了CRUD RESTful APIs。现在我们将研究如何处理上述RESTFul APIs的异常或错误。

8. RESTful服务的异常(错误)处理

Spring Boot为RESTful服务的异常处理提供了一个良好的默认实现。

让我们快速了解一下Spring Boot提供的默认异常处理功能。

资源不存在

当你向一个未找到的资源发出请求时,会发生以下情况: http://localhost:8080/some-dummy-url

{
  "timestamp": 1512713804164,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/some-dummy-url"
}

这是个很酷的错误响应。它包含所有通常需要的细节。

当我们抛出一个异常时会发生什么?

我们可以用@ResponseStatus注解来指定特定异常的响应状态以及异常的定义,让我们看看Spring Boot在资源抛出异常时做了什么。

让我们创建一个ResourceNotFoundException.java类。

package com.companyname.springbootcrudrest.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message){
        super(message);
    }
}

自定义错误响应结构

Spring Boot提供的默认错误响应包含所有通常需要的细节。

但是,你可能想为你的组织创建一个独立于框架的响应结构。在这种情况下,你可以定义一个特定的错误响应结构。
让我们来定义一个简单的错误响应Bean。

package com.companyname.springbootcrudrest.exception;

import java.util.Date;

public class ErrorDetails {
    private Date timestamp;
    private String message;
    private String details;

    public ErrorDetails(Date timestamp, String message, String details) {
        super();
        this.timestamp = timestamp;
        this.message = message;
        this.details = details;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public String getMessage() {
        return message;
   }

    public String getDetails() {
       return details;
   }
}

创建GlobalExceptionHandler类

为了使用ErrorDetails来返回错误响应,让我们创建一个带有@ControllerAdvice注解的GlobalExceptionHandler类。这个类在一个地方处理特定异常和全局异常。

package com.companyname.springbootcrudrest.exception;

import java.util.Date;

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

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> globleExcpetionHandler(Exception ex, WebRequest request) {
        ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

我们已经完成了CRUD REST APIs和REST APIs异常处理的开发。现在是时候运行这个Spring boot应用程序了。

9. 通过Application.java运行应用程序

这个spring boot应用程序有一个名为Application.java的入口点Java类,其中有*public static void main(String[] args)*方法,你可以运行它来启动该应用程序。

package net.guides.springboot2.springboot2jpacrudexample;

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

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

10. 通过Postman客户端进行测试

请注意,在上面的截图中,我们已经成功地进行了异常处理,并得到了来自REST APIs的预期响应。

11. GitHub上的源代码

这篇文章的源代码可以在我的GitHub仓库中找到。

Spring Boot JPA CRUD Example

相关文章