# SpringBoot3教程 - 14 统一返回结果和异常处理
在实际的开发中,我们的接口会很多,所以统一每个接口返回数据的结构 ,有利于更好的维护系统和数据解析。所以我们可以定义一个返回结果的类,所有的结果都通过这个类的对象类返回。
例如定义返回结果的类,,属性如下:
ResponseResult.java
public class ResponseResult<T> implements Serializable {
// 状态码
private int code;
// 信息
private String message;
// 数据
private T data;
}
2
3
4
5
6
7
8
9
10
不同接口返回的数据是不一样的,只需要将返回的数据放到data中即可。状态码 code 和信息 message 用来表示请求是否成功,失败则返回异常的相关信息。
但是程序抛出异常,返回的数据如果不经过处理,返回的数据就不是我们定义的了,格式如下:
所以我们需要对异常也要进行统一的处理,既可以确保异常被一致地处理,统一格式响应给客户端。同时还可以简化异常处理,否则会有很多像下面的代码,处理起来就很麻烦:
try {
// do something
} catch(Exception e) {
return ResponseResult.error();
}
return ResponseResult.success();
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;
}
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);
}
}
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;
}
}
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);
}
}
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());
}
}
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);
}
}
}
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));
}
}
}
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
测试请求,可以了:
上面整个项目结构如下:
← 13-过滤器配置 15-拦截并修改返回结果 →