Spring Boot——异常处理
Spring Boot 建立在 Spring 之上,包含了 Spring 的所有特性。并且由于其快速的生产就绪环境使开发人员能够直接专注于逻辑而不是为配置和设置而苦苦挣扎,如今它正成为开发人员的最爱。 Spring Boot 是一个基于微服务的框架,在其中制作可用于生产的应用程序只需要很少的时间。 Spring Boot 中的异常处理有助于处理 API 中存在的错误和异常,从而提供健壮的企业应用程序。本文介绍了在 Spring Boot 项目中处理异常的各种方法。让我们进行初始设置以更深入地探索每种方法。
初始设置
为了使用 Spring Initializer 创建一个简单的 Spring Boot 项目,请参考这篇文章。现在让我们开发一个对客户实体执行 CRUD 操作的 Spring Boot Restful Webservice。我们将使用 MYSQL 数据库来存储所有必要的数据。
第 1 步:创建一个 JPA 实体类Customer ,其中包含三个字段 id、name 和 address。
Java
// Step 1: Creating a JPA Entity
// class Customer with three
// fields id, name and address.
package com.customer.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
}
Java
// Step 2: Creating a Repository
// Interface extending
// JpaRepository
package com.customer.repository;
import com.customer.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository
extends JpaRepository {
}
Java
// Step 3: Creating custom exception
// that can be thrown when
// user tries to add a customer
// that already exists
package com.customer.exception;
public class CustomerAlreadyExistsException
extends RuntimeException {
private String message;
public CustomerAlreadyExistsException() {}
public CustomerAlreadyExistsException(String msg)
{
super(msg);
this.message = msg;
}
}
Java
// Step 3: Creating custom exception
// that can be thrown when
// user tries to update/delete a
// customer that doesn't exists
package com.customer.exception;
public class NoSuchCustomerExistsException
extends RuntimeException {
private String message;
public NoSuchCustomerExistsException() {}
public NoSuchCustomerExistsException(String msg)
{
super(msg);
this.message = msg;
}
}
Java
// Step 4: Creating service interface
package com.customer.service;
import com.customer.model.Customer;
public interface CustomerService {
// Method to get customer by its Id
Customer getCustomer(Long id);
// Method to add a new Customer
// into the database
String addCustomer(Customer customer);
// Method to update details of a Customer
String updateCustomer(Customer customer);
}
Java
// Step 4: Service class Implementation
// which defines the main logic
package com.customer.service;
// Importing required packages
import com.customer.exception.CustomerAlreadyExistsException;
import com.customer.exception.NoSuchCustomerExistsException;
import com.customer.model.Customer;
import com.customer.repository.CustomerRepository;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceImpl
implements CustomerService {
@Autowired
private CustomerRepository customerRespository;
// Method to get customer by Id.Throws
// NoSuchElementException for invalid Id
public Customer getCustomer(Long id)
{
return customerRespository.findById(id).orElseThrow(
()
-> new NoSuchElementException(
"NO CUSTOMER PRESENT WITH ID = " + id));
}
// Method to add new customer details to database.Throws
// CustomerAlreadyExistsException when customer detail
// already exist
public String addCustomer(Customer customer)
{
Customer existingCustomer
= customerRespository.findById(customer.getId())
.orElse(null);
if (existingCustomer == null) {
customerRespository.save(customer);
return "Customer added successfully";
}
else
throw new CustomerAlreadyExistsException(
"Customer already exixts!!");
}
// Method to update customer details to database.Throws
// NoSuchCustomerExistsException when customer doesn't
// already exist in database
public String updateCustomer(Customer customer)
{
Customer existingCustomer
= customerRespository.findById(customer.getId())
.orElse(null);
if (existingCustomer == null)
throw new NoSuchCustomerExistsException(
"No Such Customer exists!!");
else {
existingCustomer.setName(customer.getName());
existingCustomer.setAddress(
customer.getAddress());
customerRespository.save(existingCustomer);
return "Record updated Successfully";
}
}
}
Java
// Step 5: Creating Rest Controller CustomerController which
// defines various API's.
package com.customer.controller;
// Importing required packages
import com.customer.exception.CustomerAlreadyExistsException;
import com.customer.exception.ErrorResponse;
import com.customer.model.Customer;
import com.customer.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CustomerController {
@Autowired private CustomerService customerService;
// Get Customer by Id
@GetMapping("/getCustomer/{id}")
public Customer getCustomer(@PathVariable("id") Long id)
{
return customerService.getCustomer(id);
}
// Add new Customer
@PostMapping("/addCustomer")
public String
addcustomer(@RequestBody Customer customer)
{
return customerService.addCustomer(customer);
}
// Update Customer details
@PutMapping("/updateCustomer")
public String
updateCustomer(@RequestBody Customer customer)
{
return customerService.updateCustomer(customer);
}
}
Java
// Custom Error Response Class
package com.customer.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
private int statusCode;
private String message;
public ErrorResponse(String message)
{
super();
this.message = message;
}
}
Java
// Exception Handler method added in CustomerController to
// handle CustomerAlreadyExistsException exception
@ExceptionHandler(value
= CustomerAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse
handleCustomerAlreadyExistsException(
CustomerAlreadyExistsException ex)
{
return new ErrorResponse(HttpStatus.CONFLICT.value(),
ex.getMessage());
}
Java
// Class to handle exception Globally
package com.customer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value
= NoSuchCustomerExistsException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public @ResponseBody ErrorResponse
handleException(NoSuchCustomerExistsException ex)
{
return new ErrorResponse(
HttpStatus.NOT_FOUND.value(), ex.getMessage());
}
}
Customer类使用@Entity注释进行注释,并为字段定义 getter、setter 和构造函数。
第 2 步:创建CustomerRepository接口
Java
// Step 2: Creating a Repository
// Interface extending
// JpaRepository
package com.customer.repository;
import com.customer.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository
extends JpaRepository {
}
CustomerRepository接口使用@Repository注释进行注释,并扩展了 Spring Data JPA 的JpaRepository 。
第 3 步:创建可以在执行 CRUD 时在必要场景中抛出的自定义异常。
CustomerAlreadyExistsException:当用户尝试添加数据库中已存在的客户时,可能会引发此异常。
Java
// Step 3: Creating custom exception
// that can be thrown when
// user tries to add a customer
// that already exists
package com.customer.exception;
public class CustomerAlreadyExistsException
extends RuntimeException {
private String message;
public CustomerAlreadyExistsException() {}
public CustomerAlreadyExistsException(String msg)
{
super(msg);
this.message = msg;
}
}
NoSuchCustomerExistsException:当用户尝试删除或更新数据库中不存在的客户记录时,可能会引发此异常。
Java
// Step 3: Creating custom exception
// that can be thrown when
// user tries to update/delete a
// customer that doesn't exists
package com.customer.exception;
public class NoSuchCustomerExistsException
extends RuntimeException {
private String message;
public NoSuchCustomerExistsException() {}
public NoSuchCustomerExistsException(String msg)
{
super(msg);
this.message = msg;
}
}
Note: Both Custom Exception classes extend RuntimeException.
第四步:创建接口CustomerService并实现服务层的类CustomerServiceImpl 。
CustomerService接口定义了三种不同的方法:
- Customer getCustomer(Long id):通过 id 获取客户记录。此方法在找不到具有给定 ID 的客户记录时会引发NoSuchElementException异常。
- String addCustomer(Customer customer):将新客户的详细信息添加到数据库中。当用户尝试添加已存在的客户时,此方法会引发CustomerAlreadyExistsException异常。
- String updateCustomer(Customer customer):更新已存在客户的详细信息。当用户尝试更新数据库中不存在的客户的详细信息时,此方法会引发NoSuchCustomerExistsException异常。
接口和服务实现类如下:
Java
// Step 4: Creating service interface
package com.customer.service;
import com.customer.model.Customer;
public interface CustomerService {
// Method to get customer by its Id
Customer getCustomer(Long id);
// Method to add a new Customer
// into the database
String addCustomer(Customer customer);
// Method to update details of a Customer
String updateCustomer(Customer customer);
}
Java
// Step 4: Service class Implementation
// which defines the main logic
package com.customer.service;
// Importing required packages
import com.customer.exception.CustomerAlreadyExistsException;
import com.customer.exception.NoSuchCustomerExistsException;
import com.customer.model.Customer;
import com.customer.repository.CustomerRepository;
import java.util.NoSuchElementException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceImpl
implements CustomerService {
@Autowired
private CustomerRepository customerRespository;
// Method to get customer by Id.Throws
// NoSuchElementException for invalid Id
public Customer getCustomer(Long id)
{
return customerRespository.findById(id).orElseThrow(
()
-> new NoSuchElementException(
"NO CUSTOMER PRESENT WITH ID = " + id));
}
// Method to add new customer details to database.Throws
// CustomerAlreadyExistsException when customer detail
// already exist
public String addCustomer(Customer customer)
{
Customer existingCustomer
= customerRespository.findById(customer.getId())
.orElse(null);
if (existingCustomer == null) {
customerRespository.save(customer);
return "Customer added successfully";
}
else
throw new CustomerAlreadyExistsException(
"Customer already exixts!!");
}
// Method to update customer details to database.Throws
// NoSuchCustomerExistsException when customer doesn't
// already exist in database
public String updateCustomer(Customer customer)
{
Customer existingCustomer
= customerRespository.findById(customer.getId())
.orElse(null);
if (existingCustomer == null)
throw new NoSuchCustomerExistsException(
"No Such Customer exists!!");
else {
existingCustomer.setName(customer.getName());
existingCustomer.setAddress(
customer.getAddress());
customerRespository.save(existingCustomer);
return "Record updated Successfully";
}
}
}
第 5 步:创建定义各种 API 的 Rest Controller CustomerController 。
Java
// Step 5: Creating Rest Controller CustomerController which
// defines various API's.
package com.customer.controller;
// Importing required packages
import com.customer.exception.CustomerAlreadyExistsException;
import com.customer.exception.ErrorResponse;
import com.customer.model.Customer;
import com.customer.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CustomerController {
@Autowired private CustomerService customerService;
// Get Customer by Id
@GetMapping("/getCustomer/{id}")
public Customer getCustomer(@PathVariable("id") Long id)
{
return customerService.getCustomer(id);
}
// Add new Customer
@PostMapping("/addCustomer")
public String
addcustomer(@RequestBody Customer customer)
{
return customerService.addCustomer(customer);
}
// Update Customer details
@PutMapping("/updateCustomer")
public String
updateCustomer(@RequestBody Customer customer)
{
return customerService.updateCustomer(customer);
}
}
现在让我们来看看我们可以处理这个项目中抛出的异常的各种方法。
Spring Boot 的默认异常处理:
CustomerController定义的getCustomer()方法用于获取具有给定 ID 的客户。当找不到具有给定 ID 的客户记录时,它会抛出NoSuchElementException 。在运行 Spring Boot 应用程序并使用无效的客户 ID 访问/getCustomer API 时,我们得到一个由 Spring Boot 完全处理的NoSuchElementException ,如下所示:
Spring Boot 通过时间戳、HTTP 状态码、错误、消息和路径等信息向用户提供系统的错误响应。
使用 Spring Boot @ExceptionHandler注解:
Spring Boot 提供的@ExceptionHandler注解可用于处理特定 Handler 类或 Handler 方法中的异常。任何用 this 注释的方法都会被 Spring Configuration 自动识别为异常处理方法。异常处理程序方法处理参数中传递的所有异常及其子类。它还可以配置为向用户返回特定的错误响应。因此,让我们创建一个自定义ErrorResponse类,以便以清晰简洁的方式将异常传达给用户,如下所示:
Java
// Custom Error Response Class
package com.customer.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
private int statusCode;
private String message;
public ErrorResponse(String message)
{
super();
this.message = message;
}
}
当用户尝试添加已存在于数据库中的客户时, CustomerController定义的addCustomer()方法会引发CustomerAlreadyExistsException ,否则它会保存客户详细信息。
为了处理这个异常,让我们在CustomerController中定义一个处理方法handleCustomerAlreadyExistsException() 。所以现在当addCustomer()抛出一个CustomerAlreadyExistsException时,处理方法被调用,它返回一个正确的ErrorResponse给用户。
Java
// Exception Handler method added in CustomerController to
// handle CustomerAlreadyExistsException exception
@ExceptionHandler(value
= CustomerAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse
handleCustomerAlreadyExistsException(
CustomerAlreadyExistsException ex)
{
return new ErrorResponse(HttpStatus.CONFLICT.value(),
ex.getMessage());
}
Note: Spring Boot allows to annotate a method with @ResponseStatus to return the required Http Status Code.
在运行 Spring Boot 应用程序并使用现有客户访问/addCustomer API 时, CustomerAlreadyExistsException完全由处理程序方法处理,如下所示:
将@ControllerAdvice用于全局异常处理程序:
在前面的方法中,我们可以看到 @ExceptionHandler 注解的方法只能处理该特定类抛出的异常。但是,如果我们想处理整个应用程序中抛出的任何异常,我们可以定义一个全局异常处理程序类并使用@ControllerAdvice对其进行注释。此注释有助于将多个异常处理程序集成到一个全局单元中。
如果用户尝试更新数据库中尚不存在的客户的详细信息,则CustomerController中定义的updateCustomer()方法将引发NoSuchCustomerExistsException异常,否则它会成功保存该特定客户的更新详细信息。
为了处理这个异常,让我们定义一个用@ControllerAdvice注解的GlobalExceptionHandler类。此类定义NoSuchCustomerExistsException异常的 ExceptionHandler 方法如下。
Java
// Class to handle exception Globally
package com.customer.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value
= NoSuchCustomerExistsException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public @ResponseBody ErrorResponse
handleException(NoSuchCustomerExistsException ex)
{
return new ErrorResponse(
HttpStatus.NOT_FOUND.value(), ex.getMessage());
}
}
在运行 Spring Boot 应用程序并使用无效的客户详细信息访问/updateCustomer API 时,会抛出NoSuchCustomerExistsException ,这完全由GlobalExceptionHandler类中定义的处理程序方法处理,如下所示: