# SpringBoot3教程 - 14 统一返回结果和异常处理

在实际的开发中,我们的接口会很多,所以统一每个接口返回数据的结构 ,有利于更好的维护系统和数据解析。所以我们可以定义一个返回结果的类,所有的结果都通过这个类的对象类返回。

例如定义返回结果的类,,属性如下:

ResponseResult.java

public class ResponseResult<T> implements Serializable {
    // 状态码
    private int code;

    // 信息
    private String message;

    // 数据
    private T data;
}
1
2
3
4
5
6
7
8
9
10

不同接口返回的数据是不一样的,只需要将返回的数据放到data中即可。状态码 code 和信息 message 用来表示请求是否成功,失败则返回异常的相关信息。


但是程序抛出异常,返回的数据如果不经过处理,返回的数据就不是我们定义的了,格式如下:

所以我们需要对异常也要进行统一的处理,既可以确保异常被一致地处理,统一格式响应给客户端。同时还可以简化异常处理,否则会有很多像下面的代码,处理起来就很麻烦:

try {
    // do something
} catch(Exception e) {
    return ResponseResult.error();
}
return ResponseResult.success();
1
2
3
4
5
6

所以为了统一返回结果和异常处理,下面我们对两个方面对项目进行优化。

# 14.1 定义返回码

我们首先可以使用枚举类定义一些状态码,这样接口可以根据错误返回指定的状态码给前端。

ResponseStatus.java

package com.doubibiji.hellospringboot.constant;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 返回码枚举
 **/
@AllArgsConstructor
public enum ResponseStatus {

    /**
     * 成功
     */
    SUCCESS(200, "success"),
    UNAUTHORIZED(401 ,"用户认证失败"),
    FORBIDDEN(403 ,"权限不足"),
    NOT_FOUND(404, "请求无效"),
    SERVER_ERROR(500, "服务器异常"),

    PARAM_EMPTY(1000, "请求参数为空"),
    ACCOUNT_ERROR(1001, "用户名或密码错误");

    @Getter
    private int code;
    @Getter
    private String message;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

后面根据需要自己定义。

# 14.2 定义返回数据类

服务器通过接口返回数据,为了返回数据的格式,可以定义一个用户返回结果的类。例如定义为 ResponseResult。

ResponseResult.java

为了方便操作,定了一些静态的方法。

package com.doubibiji.hellospringboot.dto;

import com.doubibiji.hellospringboot.constant.ResponseStatus;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 * 统一返回结果类
 */
@Data
@ToString
@NoArgsConstructor
public class ResponseResult<T> implements Serializable {

    // 状态码
    private int code;

    // 信息
    private String message;

    // 数据
    private Object data;

    private ResponseResult(ResponseStatus responseStatus) {
        super();
        this.code = responseStatus.getCode();
        this.message = responseStatus.getMessage();
    }

    private ResponseResult(int code, String message) {
        super();
        this.code = code;
        this.message = message;
    }

    private ResponseResult(ResponseStatus responseStatus, T data) {
        this(responseStatus);
        this.data = data;
    }

    public static ResponseResult success () {
        return new ResponseResult(ResponseStatus.SUCCESS);
    }

    public static <T> ResponseResult success (T data) {
        return new ResponseResult(ResponseStatus.SUCCESS, data);
    }

    public static ResponseResult error(ResponseStatus responseStatus) {
        return new ResponseResult(responseStatus);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

通过 code 和 message 返回请求的是否成功的编码和信息,不同的接口返回的数据,将数据设置到 data 属性中即可。

# 14.3 定义异常类

定义自己的异常类,在项目中,可以传入不同的状态码,抛出该异常类的对象。

DoubiException.java

package com.doubibiji.hellospringboot.exception;

import com.doubibiji.hellospringboot.constant.ResponseStatus;
import lombok.Data;

@Data
public class DoubiException extends RuntimeException {

    private ResponseStatus status;

    public DoubiException(ResponseStatus status) {
        this.status = status;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 14.4 抛出异常

我们可以在 service 或 contoller 中使用异常工具类抛出自定义的异常类对象。

UserServiceImpl.java

package com.doubibiji.hellospringboot.service.impl;

import cn.hutool.core.util.StrUtil;
import com.doubibiji.hellospringboot.constant.ResponseStatus;
import com.doubibiji.hellospringboot.exception.DoubiException;
import com.doubibiji.hellospringboot.pojo.User;
import com.doubibiji.hellospringboot.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {

    /**
     * 保存用户
     */
    public User saveUser(String username, Integer age) {
        log.info("保存用户, username:{}, age:{}", username, age);

        if (StrUtil.isEmpty(username) || null == age) {
            throw new DoubiException(ResponseStatus.PARAM_EMPTY);
        }

        throw new DoubiException(ResponseStatus.ACCOUNT_ERROR);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

创建异常对象,并抛出。

# 14.5 捕获异常

在上面抛出异常了,下面需要捕获在项目中抛出的异常,然后统一处理这些异常。

使用 @RestControllerAdvice@ExceptionHandler 注解定义统一异常处理类和对应的异常处理方法。

例如定义 GlobalExceptionHandler.java,对不同类型的异常进行处理。

package com.doubibiji.hellospringboot.exception;

import com.doubibiji.hellospringboot.dto.ResponseResult;
import com.doubibiji.hellospringboot.constant.ResponseStatus;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
        log.error("统一异常处理:", exception);
        return ResponseResult.error(ResponseStatus.SERVER_ERROR);
    }

    @ExceptionHandler(value = NoResourceFoundException.class)
    public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, NoResourceFoundException exception) {
        log.error("统一异常处理:", exception);
        return ResponseResult.error(ResponseStatus.NOT_FOUND);
    }

    /**
     * 处理自定义的异常
     */
    @ExceptionHandler(value = DoubiException.class)
    public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, DoubiException exception) {
        log.error("统一异常处理, PadBotException:", exception);
        return ResponseResult.error(exception.getStatus());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 14.6 测试

下面通过访问接口,让程序抛出异常,查看一下抛出异常后的返回结果。

# 14.7 filter中的异常

但是在 filter 中抛出异常,是没有办法被统一异常处理类捕获处理的,这是因为过滤器是在请求到达Spring MVC层之前执行的,而全局异常处理器仅适用于Spring MVC层中的异常处理。

写一个测试的 filter,主动抛出异常。

package com.doubibiji.hellospringboot.filter;

import com.doubibiji.hellospringboot.constant.ResponseStatus;
import com.doubibiji.hellospringboot.exception.DoubiException;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

@Slf4j
@Component
@WebFilter(urlPatterns = "/*", filterName = "exceptionFilter")
public class ExceptionFilter implements Filter {

    @Autowired
    private HandlerExceptionResolver handlerExceptionResolver;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        try {
            System.out.println(1/0); // 抛出异常
          
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
             throw new DoubiException(ResponseStatus.SERVER_ERROR);
        }

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

请求接口,执行结果是:

不是我们定义的统一返回格式,所以这里需要借助于 HandlerExceptionResolver 类进行手动抛出。

package com.doubibiji.hellospringboot.filter;

import com.doubibiji.hellospringboot.constant.ResponseStatus;
import com.doubibiji.hellospringboot.exception.DoubiException;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

@Slf4j
@Component
@WebFilter(urlPatterns = "/*", filterName = "exceptionFilter")
public class ExceptionFilter implements Filter {

    @Autowired
    private HandlerExceptionResolver handlerExceptionResolver;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        try {
            System.out.println(1/0); // 抛出异常
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            // 使用handlerExceptionResolver抛出异常!!!
            handlerExceptionResolver.resolveException((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse,
                    null, new DoubiException(ResponseStatus.SERVER_ERROR));
        }

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

测试请求,可以了:



上面整个项目结构如下: