这篇文章,主要介绍如何利用SpringBoot框架实现统一异常处理、统一结果响应、统一参数校验。
目录
一、SpringBoot统一结果响应
1.1、创建工程
1.2、配置FastJson
1.3、创建ResultEnum枚举
1.4、创建Result实体类
二、SpringBoot统一异常处理
2.1、创建自定义异常类
2.2、创建全局异常处理类
三、SpringBoot统一参数校验
3.1、引入参数校验依赖
3.2、创建测试实体类
3.3、创建测试控制器
3.4、运行测试
在实际的项目开发过程中,当应用程序发生异常时,用户可以接收到友好的错误提示信息,而不是直接出现一大堆看不懂的错误信息,这就需要对应用程序的所有异常进行统一的处理,SpringBoot框架提供了统一异常处理的注解,通过相应的注解就可以捕获所有可能出现的异常信息。
此外,在前后端分离的模式下,数据的交互都是采用JSON格式来传递的,如果每一个方法都返回不同格式的数据,这显然不太合适,所以,实际开发中都会将响应结果统一处理,并且对前后端传递的参数进行统一的校验。下面具体介绍如何实现三个统一。
一、SpringBoot统一结果响应
统一结果响应,这里是采用的JSON格式响应所有的数据,所以需要使用FastJson依赖。
1.1、创建工程
首先,创建一个SpringBoot工程,工程中需要引入下面几个依赖。
<!-- 引入 SpringBoot 父工程依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<dependencies>
<!-- 引入 web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除 jackson 依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入 fastjson 依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.77</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
1.2、配置FastJson
在工程里面,创建一个【CustomFastJsonConfig】配置类,添加如下配置。
package com.spring.boot.demo.common.config;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:07
* @Description FastJson 配置类
*/
@Configuration
public class CustomFastJsonConfig {
@Bean
public HttpMessageConverters fastjsonHttpMessageConverters() {
// 创建 FastJsonHttpMessageConverter 消息转换器对象
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
// 创建 FastJsonConfig 配置类对象
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 设置编码字符集
fastJsonConfig.setCharset(StandardCharsets.UTF_8);
// 设置日期格式
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// 设置序列化特征: SerializerFeature 是一个枚举,可以选择不同的序列化特征
SerializerFeature[] serializerFeatures = new SerializerFeature[]{
// WriteNullStringAsEmpty: 如果字符串等于 null,那么会被序列化成空字符串 ""
SerializerFeature.WriteNullStringAsEmpty,
// WriteNullNumberAsZero: 如果数字等于 null,那么会被序列化成 0
SerializerFeature.WriteNullNumberAsZero,
// WriteNullBooleanAsFalse: 如果布尔类型等于 null,那么会被序列化成 false
SerializerFeature.WriteNullBooleanAsFalse,
// PrettyFormat: 美化JSON
SerializerFeature.PrettyFormat
};
fastJsonConfig.setSerializerFeatures(serializerFeatures);
// 配置添加到消息转换器里面
fastJsonHttpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
// 设置响应JSON格式数据
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_JSON); // JSON 格式数据
// 设置消息转换器支持的格式
fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypeList);
// 返回消息转换器
return new HttpMessageConverters(fastJsonHttpMessageConverter);
}
}
1.3、创建ResultEnum枚举
在一个工程里面,可能会出现很多不同的响应结果状态,所以这里采用一个【ResultEnum】枚举类来保存。
- 这里可以根据自己的需要,继续添加不同的枚举类型。
package com.spring.boot.demo.common.resp;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:13
* @Description 统一响应结果枚举类
*/
public enum ResultEnum {
SUCCESS(20000, "响应成功"),
FAILED(50000, "操作异常");
/** 状态码 */
private Integer statusCode;
/** 提示信息 */
private String message;
ResultEnum(int statusCode, String message) {
this.statusCode = statusCode;
this.message = message;
}
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
1.4、创建Result实体类
为了实现统一响应结果,这里创建一个【Result】类,该类作为所有控制器方法的返回值类型。
package com.spring.boot.demo.common.resp;
import java.io.Serializable;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:16
* @Description 统一响应结果类
*/
public class Result<T> implements Serializable {
/** 状态码 */
private Integer statusCode;
/** 提示信息 */
private String message;
/** 响应数据 */
private T data;
/** 私有构造方法 */
private Result() {}
/************************** common method **************************/
/* 响应成功 */
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setStatusCode(ResultEnum.SUCCESS.getStatusCode());
result.setMessage(ResultEnum.SUCCESS.getMessage());
return result;
}
/* 响应失败 */
public static <Void> Result<Void> failure() {
Result<Void> result = new Result<>();
result.setStatusCode(ResultEnum.FAILED.getStatusCode());
result.setMessage(ResultEnum.FAILED.getMessage());
return result;
}
/* 响应失败 */
public static <Void> Result<Void> failure(String message) {
Result<Void> result = new Result<>();
result.setStatusCode(ResultEnum.FAILED.getStatusCode());
result.setMessage(message);
return result;
}
/* 响应失败 */
public static <Void> Result<Void> failure(int statusCode, String message) {
Result<Void> result = new Result<>();
result.setStatusCode(statusCode);
result.setMessage(message);
return result;
}
/* 响应失败 */
public static <Void> Result<Void> failure(ResultEnum resultEnum) {
Result<Void> result = new Result<>();
result.setStatusCode(resultEnum.getStatusCode());
result.setMessage(resultEnum.getMessage());
return result;
}
/* 自定义响应状态码 */
public Result<T> statusCode(int statusCode) {
this.setStatusCode(statusCode);
return this;
}
/* 自定义响应提示信息 */
public Result<T> message(String message) {
this.setMessage(message);
return this;
}
/* 自定义响应数据 */
public Result<T> data(T data) {
this.setData(data);
return this;
}
/************************** getter and setter **************************/
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"statusCode=" + statusCode +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
到这里,我们就已经把统一响应结果的类定义好啦,下面继续定义统一全局异常处理的类。
二、SpringBoot统一异常处理
2.1、创建自定义异常类
在工程里面,创建一个自定义的异常类【BizException】,这个类主要用于我们在编写业务代码的时候,自定义设置错误的状态码和提示信息。
package com.spring.boot.demo.common.exception;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:30
* @Description 自定义异常类
*/
public class BizException extends RuntimeException {
/** 状态码 */
private Integer statusCode;
/** 提示信息 */
private String message;
public BizException(int statusCode, String message) {
this.statusCode = statusCode;
this.message = message;
}
public BizException() {
super();
}
public BizException(String message) {
this.message = message;
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(Throwable cause) {
super(cause);
}
protected BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2.2、创建全局异常处理类
在工程里面,创建一个【GlobalExceptionHandler】类,这个类就是处理整个项目里面出现的所有异常的。
- 这个类上面,需要使用【@ControllerAdvice】注解,表示该类会通过AOP的方式拦截所有Controller层抛出的所有异常。
- 在该类里面,定义处理异常的方法,并且使用【@ExceptionHandler】注解,指定当前方法处理的异常类型。
package com.spring.boot.demo.common.exception;
import com.spring.boot.demo.common.resp.Result;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:00
* @Description 全局异常处理类
*/
@ControllerAdvice // 指定AOP拦截Controller层方法
public class GlobalExceptionHandler {
/**
* 统一异常处理
*/
@ExceptionHandler(value = {Exception.class})
@ResponseBody
public Result<Void> commonHandler(Exception e) {
e.printStackTrace();
// 返回错误信息
return Result.failure(e.getMessage());
}
/**
* 捕获我们自定义的异常
*/
@ExceptionHandler(value = {BizException.class})
@ResponseBody
public Result<Void> bizExceptionHandler(BizException e) {
e.printStackTrace();
// 返回错误信息
return Result.failure(e.getMessage());
}
/** 参数校验异常处理 */
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Result<Void> exception(MethodArgumentNotValidException e) {
e.printStackTrace();
// 保存错误提示信息
String message = "参数校验失败";
// 从异常对象中拿到 ObjectError 对象
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
if (!CollectionUtils.isEmpty(allErrors)) {
// 返回第一个校验失败的参数名称
message = allErrors.get(0).getDefaultMessage();
}
// 返回参数校验信息
return Result.failure(message);
}
}
到这里,全局异常处理类就创建好啦,下面继续创建统一参数校验(注意:上面处理参数校验异常的时候,会报错,那是因为没有引入下面两个参数校验的依赖)。
三、SpringBoot统一参数校验
3.1、引入参数校验依赖
参数校验需要引入validation依赖,如下所示:
<!-- validation参数校验 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!-- validation参数校验 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
3.2、创建测试实体类
package com.spring.boot.demo.pojo;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:45
* @Description
*/
public class User implements Serializable {
@NotBlank(message = "用户名称不能为空")
@Size(min = 1, max = 50, message = "用户名称长度必须在1到50之间")
private String username;
@NotBlank(message = "用户密码不能为空")
@Size(min = 1, max = 30, message = "用户密码长度必须在1到30之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
}
3.3、创建测试控制器
package com.spring.boot.demo.controller;
import com.spring.boot.demo.common.exception.BizException;
import com.spring.boot.demo.common.resp.Result;
import com.spring.boot.demo.pojo.User;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* @author ZhuYouBin
* @version 1.0.0
* @Date: 2022/10/29 21:44
* @Description
*/
@RestController
@RequestMapping("/api")
public class TestController {
@GetMapping("/success")
public Result<List<User>> success() {
List<User> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
User user = new User();
user.setUsername("text-name00" + i);
user.setPassword("pass-00" + i);
user.setEmail("test@qq.com");
list.add(user);
}
return Result.<List<User>>success().data(list);
}
@GetMapping("/error")
public Result<Void> failure() {
int i = 10;
// 模拟异常情况
int ans = i / 0;
return Result.success();
}
@GetMapping("/error02")
public Result<Void> failure02() {
int i = 10;
// 模拟异常情况
try {
int ans = i / 0;
} catch (Exception e) {
throw new BizException("抛出自定义异常");
}
return Result.success();
}
@PostMapping("/error03")
public Result<User> param(@RequestBody @Valid User user) {
// 测试参数校验异常, 必须使用【@Valid】注解生效
return Result.<User>success().data(user);
}
}
3.4、运行测试
启动工程,使用Postman一次访问上面四个请求地址,可以看到返回结果。
到此,SpringBoot统一异常处理、统一结果响应、统一参数校验就成功啦。
综上,这篇文章结束了,主要介绍如何利用SpringBoot框架实现统一异常处理、统一结果响应、统一参数校验。