Spring Boot 中的异常处理
Spring Boot 中的异常处理有助于处理 API 中存在的错误和异常,从而提供强大的企业应用程序。本文介绍了处理异常的各种方法以及如何在 Spring Boot 项目中向客户端返回有意义的错误响应。
以下是 Spring Boot 中异常处理的一些主要方法:
- Spring Boot 的默认异常处理
- 使用 @ExceptionHandler 注解
- 用于 @ControllerAdvice 全局异常处理
让我们进行初始设置,以更深入地探索每种方法。
初始设置
要使用 Spring Initializer 创建一个简单的 Spring Boot 项目,请参阅本文。现在让我们开发一个 Spring Boot Restful Web 服务,对客户实体执行 CRUD 操作。我们将使用 MYSQL 数据库来存储所有必要的数据。
步骤 1:创建 JPA 实体类 Customer
Java
// 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;
}
Customer 类用@Entity注释进行注释,并为字段定义 getter、setter 和构造函数。
步骤 2:创建 CustomerRepository 界面
Java
// 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<Customer, Long> {
}
CustomerRepository 接口用@Repository注释,并扩展了Spring Data JPA的 JpaRepository 。
步骤 3:创建自定义异常
CustomerAlreadyExistsException:当用户尝试添加数据库中已存在的客户时,可能会引发此异常。
Java
// Creating a custom exception that can be thrown when a 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:当用户尝试删除或更新数据库中不存在的客户记录时,可能会引发此异常。
NoSuchCustomerExistsException :
Java
// Creating a custom exception that can be thrown when a user tries to update/delete a customer that doesn't exist
package com.customer.exception;
public class NoSuchCustomerExistsException extends RuntimeException {
private String message;
public NoSuchCustomerExistsException() {}
public NoSuchCustomerExistsException(String msg) {
super(msg);
this.message = msg;
}
}
注意:两个自定义异常类都扩展了RuntimeException。
步骤 4:创建服务层
CustomerService 接口定义了三种不同的方法:
- Customer getCustomer(Long id):通过 id 获取客户记录。如果找不到具有指定 id 的客户记录,此方法将抛出 NoSuchElementException 异常。
- String addCustomer(Customer customer):将新客户的详细信息添加到数据库。当用户尝试添加已存在的客户时,此方法将引发 CustomerAlreadyExistsException 异常。
- String updateCustomer(Customer customer):更新已存在客户的详细信息。当用户尝试更新数据库中不存在的客户的详细信息时,此方法将引发 NoSuchCustomerExistsException 异常。
接口和服务实现类如下:
CustomerService 接口:
Java
// 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);
}
CustomerServiceImpl 实现:
Java
// Implementing the service class
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 exists!!");
}
// 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:创建 CustomerController
Java
// 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 状态代码、错误、消息和路径等信息。
使用 @ExceptionHandler 注解
- Spring Boot 提供的@ExceptionHandler注解可以用来处理特定的Handler 类或Handler 方法中的异常。
- 任何以此注释的方法都会被 Spring 配置自动识别为异常处理程序方法。
- 异常处理程序方法处理在参数中传递的所有异常及其子类。
- 它还可以配置为向用户返回特定的错误响应。
因此,让我们创建一个自定义的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
@ExceptionHandler(value = CustomerAlreadyExistsException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorResponse handleCustomerAlreadyExistsException(CustomerAlreadyExistsException ex) {
return new ErrorResponse(HttpStatus.CONFLICT.value(), ex.getMessage());
}
注意:Spring Boot 允许使用@ResponseStatus注释方法来返回所需的 Http 状态代码。
在运行 Spring Boot 应用程序并使用现有客户点击/addCustomer API 时, CustomerAlreadyExistsException将由处理程序方法完全处理,如下所示:
用于 @ControllerAdvice 全局异常处理
在以前的方法中,注释的方法只能处理该特定类抛出的异常。但是,如果我们想处理整个应用程序抛出的任何异常,我们可以定义一个全局异常处理程序类并用 注释它。此注释有助于将多个异常处理程序集成到单个全局单元中。 @ExceptionHandler @ControllerAdvice
如果用户尝试更新数据库中尚不存在的客户详细信息,则中定义的方法将抛出 。要处理此异常,请定义一个用 注释的类。 updateCustomer() CustomerController NoSuchCustomerExistsException GlobalExceptionHandler @ControllerAdvice
Java
// Class to handle exceptions 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.NOT_FOUND)
public @ResponseBody ErrorResponse handleException(NoSuchCustomerExistsException ex) {
return new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
}
}
在运行 Spring Boot 应用程序并 /updateCustomer 使用无效的客户详细信息访问 API 时, NoSuchCustomerExistsException 会抛出该错误,该错误完全由类中定义的处理程序方法处理, GlobalExceptionHandler 如下所示:
最后
Spring Boot 提供了多种处理异常的方法,确保您的应用程序能够返回有意义的错误。