springboot 加 @ControllerAdvice 或 ErrorController 全局异常处理, 返回页面 或 Ajax请求的 json数据
方案一 springboot 加 @ControllerAdvice 统一异常处理, 返回页面 或者 Ajax请求的 json数据
package com.blogfrontend.controller;
import com.blogcommon.utils.result.Result;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
public class ExceptionAdviceController{
/**
*
* @param request
* @param e
* @param model
* @return
*/
@ExceptionHandler(Exception.class)
// HTTP状态代码强制返回500
// @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Object exception(HttpServletRequest request, Exception e, Model model) {
if(this.isAjaxRequest(request)){
return this.Json(e);
}else {
return this.view(request, e, model);
}
}
/**
*
* @param request
* @param e
* @param model
* @return
*/
private ModelAndView view(HttpServletRequest request, Exception e, Model model){
Integer statusCode = getStatus(request);
String viewName = "error/404";
switch (statusCode) {
case 404:
viewName = "error/404";
case 403:
viewName = "error/403";
case 500:
viewName = "error/500";
}
ModelAndView modelAndView = new ModelAndView(viewName);//配置需要跳转的Controller方法
request.setAttribute("message", "系统异常");
return modelAndView;
}
/**
*
* @param e
* @return
*/
private ModelAndView Json(Exception e){
ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
Result ret = Result.fail(e.getMessage());
modelAndView.addObject("message", ret.getMessage());
modelAndView.addObject("code", ret.getCode());
modelAndView.addObject("data", ret.getData());
modelAndView.addObject("success", ret.isSuccess());
return modelAndView;
}
/**
*
* @param request
* @return
*/
private Boolean isAjaxRequest(HttpServletRequest request){
String contentType = request.getHeader("Accept");
return (contentType != null) && (contentType.contains("application/json"));
}
/**
*
* @param request
* @return
*/
private Integer getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return 500;
}
return statusCode;
}
}
方案二 实现 ErrorController
接口
在开发中,当我们需要构建一个基础的开发框架时,自定义错误页面、全局异常处理、响应结果集封装等技术点,是我们必须考虑的问题,那么如何在SpringBoot框架的基础上来优雅的解决这些问题呢?
一、自定义错误页面
关于错误页面的处理,SpringBoot提供了默认的支持,基本思路如下:
1、当页面请求发生异常时,会自动请求“/error”控制器;
2、此控制器会根据处理类型自动匹配,将处理类型为“text/html”的异常请求特殊处理,其他的异常请求默认处理。
3、特殊处理的方式为:根据响应码去查找对应的错误页面,例如,响应码为404,则系统会会在以下两个路径去查找页面:
模板路径:/templates/error/404.html
静态资源路径:/resources/META-INF/resources/error/404.html
如果两者都不存在,则系统会自动返回一个名称为“error”的视图,如果视图名称对应的模板页面(/templates/error.html)不存在,则系统会自动拼接一个错误页面响应给客户端,如下:
4、默认处理方式为:除处理类型为“text/html”的请求外,其他异常请求,系统统一返回json格式数据,数据格式如下:
{
"timestamp": "2020-03-06T05:01:50.054+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/x/x"
}
通过以上对SpringBoot异常请求的默认处理分析,可知,要想自定义异常请求的处理,大致有两种方式: 1、依赖默认处理方式,只自定义错误页面内容,这种方式最简单,缺点是不够灵活。 2、自定义异常请求控制器,将所有的异常请求转发至此控制器处理,这种方式比较灵活,也最常用。
方式一、自定义错误页面
如果你想只定义一个错误页面,只需在templates目录下定义一个error.html即可,所有的异常请求将会自动跳转至此页面。 如果你想根据状态码来区分错误页面,只需在templates/error目录下,根据状态码定义错误页面,例如:
/error/404.html
/error/400.html
/error/500.html
方式二、自定义异常控制器
自定义异常控制器,是最为灵活的控制错误页面的方式,如下
@Controller
public class ExceptionController implements ErrorController {
public static Logger LOG = LoggerFactory.getLogger(ExceptionController.class);
//默认请求地址
private static final String ERROR_PATH = "/error";
@Autowired
private ErrorAttributes errorAttributes;
/**
* web页面异常请求处理
* @param request
* @return
*/
@RequestMapping(value = ERROR_PATH, produces = MediaType.TEXT_HTML_VALUE)
public String handlePageError(HttpServletRequest request) {
LOG.info("进入异常跳转");
Integer statusCode = getStatus(request);
switch (statusCode) {
case 404:
LOG.info("404异常跳转");
return "error/404";
case 403:
LOG.info("403异常跳转");
return "error/403";
case 500:
LOG.info("500异常跳转");
return "error/500";
default:
LOG.info("默认异常跳转");
return "error/404";
}
}
/**
* 其他异常请求处理,例如:contentType:application/json等
* @param request
* @return
*/
@RequestMapping(ERROR_PATH)
@ResponseBody
public Result handleAllError(HttpServletRequest request) {
WebRequest webRequest = new ServletWebRequest(request);
Integer statusCode = getStatus(request);
Map<String, Object> body = this.errorAttributes.getErrorAttributes(webRequest, false);
body.put("status", statusCode);
return ResultUtil.result(CodeMsgFactory.REQ_EXCEPTION, body);
}
/**
* 获取状态码
* @param request
* @return
*/
public Integer getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return 500;
}
return statusCode;
}
/**
* Spring 默认错误页路径
* @return
*/
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}
以上异常控制器,将请求状态返回码为403、404、500的请求分别指向了与之对应的错误页面,将无法预知的状态码默认指向404的错误页,当然这个可以根据自己的业务情况而定。
二、响应结果集封装
响应结果集封装,主要的应用场景是Api请求,现在主流的Api请求方式是http-json,而比较主流的接口交互方式是Restful,在这种交互模式下,我们对接口响应做统一封装,可以使得代码更加规范,接口响应数据的解析更为简洁。
1、结果集封装对象
public class Result {
private String code;
private String msg;
private Object data;
public Result(CodeMsg codeMsg, Object data) {
this.code = codeMsg.getCode();
this.msg = codeMsg.getMsg();
this.data = data;
}
public Result(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2、响应码信息封装
public class CodeMsg {
private String code;
private String msg;
public CodeMsg(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3、响应码对象静态工厂
public class CodeMsgFactory {
/**
* 系统通用结果码
*/
public static final CodeMsg SUCC = new CodeMsg("000000", "成功");
public static final CodeMsg FAIL = new CodeMsg("FFFFFF", "系统异常");
/**
* 请求参数错误
*/
public static final CodeMsg REQ_PARAM_ERROR = new CodeMsg("REQ_PARAM_ERROR", "请求参数错误");
/**
* 请求异常
*/
public static final CodeMsg REQ_EXCEPTION = new CodeMsg("REQ_EXCEPTION", "请求异常");
}
4、结果集工具类
public class ResultUtil {
/**
* 成功
* @return
*/
public static Result succ() {
return succ(null) ;
}
/**
* 成功
* @return
*/
public static Result succ(Object data) {
return new Result(CodeMsgFactory.SUCC, data) ;
}
/**
* 失败
* @return
*/
public static Result fail() {
return fail(null) ;
}
public static Result fail( Object data) {
return new Result(CodeMsgFactory.FAIL, data) ;
}
public static Result result(CodeMsg codeMsg, Object data) {
return new Result(codeMsg, data) ;
}
public static Result result(CodeMsg codeMsg) {
return new Result(codeMsg, null) ;
}
public static Result result(String code, String msg, Object data) {
CodeMsg codeMsg = new CodeMsg(code, msg);
return new Result(codeMsg, data) ;
}
通过以上四个类,就可以完成响应结果集的封装,这种封装方式虽然不是最简单的,但是个人认为是比较实用的方式,当然也可以通过枚举封装响应码信息,这个在之前Java基础部分文章中已经提到过,就不赘述了。
三、全局异常处理
全局异常处理的主要应用场景也是Api请求,例如:当我们在service中处理业务流程时,需要向接口请求返回一些异常信息,但是局限于service方法的返回值无法接收此类信息,此时使用全局异常就是最好的解决方案。我们可以定义一个RuntimeException类型的业务异常,由于此类异常不需要做异常处理,所以对我们原有的代码没有任何的侵入,只需在需要返回异常信息的位置,手动抛出一个业务异常,这个业务异常携带着我们事先定义好的响应码对象,最终由全局异常处理器进行捕捉,通过响应结果集工具类返回给接口请求。
1、定义业务异常
/**
* 定义系统异常类
*/
public class SysException extends RuntimeException {
/**
* 异常码及对应异常信息
*/
private CodeMsg codeMsg;
/**
* 无参构造
*/
public SysException () {}
/**
* 自定义异常信息
* @param codeMsg
*/
public SysException(CodeMsg codeMsg) {
super(codeMsg.getMsg());
this.codeMsg = codeMsg;
}
/**
* 自定义异常信息
* @param codeMsg
*/
public SysException(CodeMsg codeMsg, Exception e) {
super(codeMsg.getMsg());
this.codeMsg = codeMsg;
e.printStackTrace();
}
/**
* 获取异常信息
* @return
*/
public CodeMsg getCodeMsg() {
return codeMsg;
}
}
2、全局异常处理器
@Component
@ControllerAdvice
public class ExceptionHandle {
/**
* 系统异常处理
* @param e
* @return
*/
@ExceptionHandler(value = SysException.class)
@ResponseBody
public Object systemExceptionHandle(SysException e) {
e.printStackTrace();
return ResultUtil.result(e.getCodeMsg());
}
}
@ControllerAdvice
,其实就是一个增强的控制器,它可以对controller中被 @RequestMapping
注解的方法加一些逻辑处理,常见的用法有:异常处理,数据绑定,数据预处理等操作,这里我们使用的就是它的异常处理功能。