首页 » java

springboot 加 @ControllerAdvice 或 ErrorController 全局异常处理, 返回页面 或 Ajax请求的 json数据

   发表于:java评论 (0)   热度:358

方案一   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注解的方法加一些逻辑处理,常见的用法有:异常处理,数据绑定,数据预处理等操作,这里我们使用的就是它的异常处理功能。

(。・v・。)
喜欢这篇文章吗?欢迎分享到你的微博、QQ群,并关注我们的微博,谢谢支持。