spring Boot手把手教学(7): 抛弃try-catch, 如何优雅统一处理异常(包含404处理)
1、前言
我们在项目开发中,难免碰到业务代码异常,无论是server 500
, 还是其他异常。
我们这里简单说一下,如何抛弃try-catch
,统一进行异常响应处理;
❝错误类型:
1、自定义业务服务代码异常(根据各自项目需求)
2、ServletException HTTP请求异常
3、内部代码异常:比如 mysql 查询表名错误
4、请求接口404:这个在统一异常中无法获取,需要额外处理
❞
正常情况下,我们需要这么写:
❝JobController
❞
// 查询所有
@GetMapping("/list")
public Result getList() {
Result result;
try {
List<Job> jobList = jobService.findAll();
result = ResultGenerator.getSuccessResult(jobList);
} catch (Exception e) {
e.printStackTrace();
result = ResultGenerator.getFailResult(e.getMessage());
}
return result;
}
结果如图:

这样比较麻烦,要写很多try-catch.
那么有没有一种办法,帮我们自动拦截错误呢?
2、自定义异常处理
HandlerExceptionResolver
, 就是处理异常的类;
❝源码:
❞
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}
以上有四个参数
其实相当于:request
(请求)、response
(响应)、hendler
(处理器)、exception
(异常);
接下来我们重写 WebMvcConfigurer.configureHandlerExceptionResolvers
来处理异常:
❝com.scaffold.test.config.WebMvcConfig
❞
package com.scaffold.test.config;
import com.alibaba.fastjson.JSON;
import com.scaffold.test.base.Result;
import com.scaffold.test.base.ResultCode;
import com.scaffold.test.base.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
// 统一异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add((request, response, handler, e) -> {
Result result = new Result();
// 错误处理
if (e instanceof ServiceException) {
// 1、业务失败的异常,如“账号或密码错误”
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
logger.info(e.getMessage());
} else if (e instanceof ServletException) {
// 2、调用失败
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
} else {
// 3、内部错误
result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
result.setMessage(message);
logger.error(message, e);
}
responseResult(response, result);
return new ModelAndView();
});
}
// 处理返回数据格式
private void responseResult(HttpServletResponse response, Result result) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
}
❝1、业务失败的异常: ServiceException 服务异常类
❞
package com.scaffold.test.base;
/**
* 服务(业务)异常如“ 账号或密码错误 ”,该异常只做INFO级别的日志记录 @see WebMvcConfigurer
*/
public class ServiceException extends RuntimeException {
public ServiceException() {
}
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
}
// 部分代码
if (e instanceof ServiceException) {
// 1、业务失败的异常,如“账号或密码错误”
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
logger.info(e.getMessage());
}
报错400代码;
❝2、调用失败: Servlet异常
❞
if (e instanceof ServletException) {
// 3、调用失败
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
}
结果如图:

❝3、内部代码异常:比如 mysql 查询表名错误
❞
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
result.setMessage(message);
logger.error(message, e);
结果如图:

❝4、请求接口404:这个在统一异常中无法获取,需要额外处理
❞
404进入不了统一异常处理,如下图:

所以,全局异常处理捕获不到404错误,需要专门写一个类用来处理404异常
❝com.scaffold.test.base.NotFoundException
❞
package com.scaffold.test.base;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotFoundException implements ErrorController {
private static final String ERROR_PATH = "/error";
@RequestMapping(ERROR_PATH)
public Result error() {
Result result = new Result();
// 2、接口不存在
result.setCode(ResultCode.NOT_FOUND).setMessage("接口不存在");
return result;
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}

3、完整代码
❝com.scaffold.test.base.Result: 统一返回实体类
❞
package com.scaffold.test.base;
import lombok.Data;
/**
* 统一API响应结果封装
*/
@Data
public class Result {
private int code;
private String message = "success";
private Object data;
public Result setCode(ResultCode resultCode){
this.code = resultCode.code;
return this;
}
public Result setMessage(String message){
this.message = message;
return this;
}
public Result setData(Object data){
this.data = data;
return this;
}
}
❝com.scaffold.test.base.ResultCode: 统一返回响应码枚举,参考HTTP状态码
❞
package com.scaffold.test.base;
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误
public int code;
ResultCode(int code) {
this.code = code;
}
}
❝统一异常拦截:com.scaffold.test.config.WebMvcConfig
❞
package com.scaffold.test.config;
import com.alibaba.fastjson.JSON;
import com.scaffold.test.base.Result;
import com.scaffold.test.base.ResultCode;
import com.scaffold.test.base.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final Logger logger = LoggerFactory.getLogger(WebMvcConfigurer.class);
// 统一异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add((request, response, handler, e) -> {
Result result = new Result();
// 异常处理
if (e instanceof ServiceException) {
// 1、业务失败的异常,如“账号或密码错误”
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
logger.info(e.getMessage());
}else if (e instanceof ServletException) {
// 2、调用失败
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
} else {
// 3、内部其他错误
result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
result.setMessage(message);
logger.error(message, e);
}
responseResult(response, result);
return new ModelAndView();
});
}
// 处理响应数据格式
private void responseResult(HttpServletResponse response, Result result) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(result));
} catch (IOException ex) {
logger.error(ex.getMessage());
}
}
}
❝404拦截处理:com.scaffold.test.base.NotFoundException
❞
package com.scaffold.test.base;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotFoundException implements ErrorController {
private static final String ERROR_PATH = "/error";
@RequestMapping(ERROR_PATH)
public Result error() {
Result result = new Result();
// 4、接口不存在
result.setCode(ResultCode.NOT_FOUND).setMessage("接口不存在");
return result;
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}
本文使用 mdnice 排版
转载自:https://juejin.cn/post/6844904193182941192