SpringBoot2.0实战 | 第九章:全局异常处理统一格式输出

第八章:整合hibernate-validator优雅表单校验
我们使用了 hibernate-validator 对参数进行校验,
如果校验失败,Spring 会使用默认的异常处理器对校验失败抛出的异常进行处理,并返回给调用端,
但如果希望返回的格式是自定义的格式,则需要自行设置全局异常处理器

课程目标

对 SpringBoot 项目中的所有异常进行响应,处理成统一的格式进行输出。

操作步骤

添加依赖

引入 Spring Boot Starter 父工程

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

整体依赖如下所示

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

编码

  1. 构建统一输出类,作为项目所有接口的输出对象
@Getter
@Setter
@NoArgsConstructor
public class RestData<T> {

    private static final String SUCCESS_INFO = "操作成功";
    private static final String FAIL_INFO = "操作失败";

    // 接口调用状态,true表示调用成功,false表示调用失败
    private boolean status;
    // 提示信息,如果没有设置,则根据status值使用默认提示
    private String info;
    // 返回数据
    private T data;

    private RestData(boolean status, String info) {
        this.status = status;
        this.info = info;
    }

    /**
     * 操作是否成功
     */
    public boolean isSuccess() {
        return this.status;
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> success() {
        return this.success(null);
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> success(String info) {
        return success(info, null);
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> success(String info, T data) {
        if (info != null && !info.isEmpty()) {
            this.status = true;
            this.info = info;
            this.data = data;
            return this;
        } else {
            return success(SUCCESS_INFO, data);
        }
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> error() {
        return this.error(null);
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> error(String info) {
        return error(info, null);
    }

    /**
     * 操作成功,组装返回数据
     */
    public RestData<T> error(String info, T data) {
        if (info != null && !info.isEmpty()) {
            this.status = false;
            this.info = info;
            this.data = data;
            return this;
        } else {
            return error(FAIL_INFO, data);
        }
    }

}
  1. 全局异常处理器
  • 类上添加 @RestControllerAdvice 注解,是 @ResponseBody@ControllerAdvice 的结合,其中 @ControllerAdvice 用于注册异常处理器,而 @ResponseBody 用于标记该类中所有方法返回类型为 JSON。
  • 方法上添加 @ExceptionHandler 注解,用于标记该方法用于处理何种异常。
  • 该类中可以同时编写多个方法,用于处理多种异常,Spring 会自行根据异常类型选择执行相应的方法。
package com.mhkj.config;

import com.mhkj.utils.RestData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {

    /**
     * 校验错误拦截处理
     * 使用 @RequestBody 接收入参时,校验失败抛 MethodArgumentNotValidException 异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public RestData handler(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException handler", e);
        BindingResult bindingResult = e.getBindingResult();
        if (bindingResult.hasFieldErrors()) {
            return new RestData().error(bindingResult.getFieldError().getDefaultMessage());
        }
        return new RestData().error("parameter is not valid");
    }

    /**
     * 校验错误拦截处理
     * 使用 @RequestBody 接收入参时,数据类型转换失败抛 HttpMessageConversionException 异常
     */
    @ExceptionHandler(value = HttpMessageConversionException.class)
    public RestData handler(HttpMessageConversionException e) {
        log.error("HttpMessageConversionException handler", e);
        return new RestData().error(e.getMessage());
    }

    /**
     * 全局异常处理
     */
    @ExceptionHandler(value = Exception.class)
    public RestData handler(Exception e) {
        log.error("exception handler", e);
        return new RestData().error(e.getMessage());
    }

}
  1. Controller 层代码
@RestController
public class UserController {

    @RequestMapping("/register")
    public String doRegister(@Valid @RequestBody UserBO bo) {
        return "success";
    }

}
  1. 启动类
@SpringBootApplication
public class Application {

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

}

验证结果

编写测试用例

@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = Application.class)
public class UserTest {

    private MockMvc mvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void test1() throws Exception {
        MvcResult mvcResult = mvc.perform(
                MockMvcRequestBuilders
                .post("/user/add")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content("{\"name\":\"user1\",\"sex\":1,\"birthday\":\"2030-05-21\"}")
        )
        .andDo(MockMvcResultHandlers.print())
        .andReturn();

        Assert.assertEquals(200, mvcResult.getResponse().getStatus());
    }

}

调用测试用例可以看到输出如下所示,因为入参不符合校验规则,所以 hiberenate-validator 返回了一个异常,该异常被全局异常处理器捕获,处理后返回调用方

{"status":false,"info":"用户等级不能为空","data":null,"success":false}

源码地址

本章源码 : https://gitee.com/gongm_24/spring-boot-tutorial.git

结束语

通过全局异常处理,可以保证系统在出现异常的情况下,出参结构是统一的,结合接口正常情况下的返回结构,就可以保证整个系统的出参结构一致,这种方式不管是跟前端交互还是跟其它系统交互,都很重要。

发布了153 篇原创文章 · 获赞 22 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/gongm24/article/details/103241620