由于错误在所难免,异常处理已经成为开发工作中不可或缺的部分。

在web开发中,我们通常不希望用户看到一个写满StackTrace的错误页面;同时,我们希望出现错误或发生异常时,开发运维人员可以看到详细的错误信息,以便进行查错和DEBUG。

所以,在开发过程中,应重视异常处理。在进行业务逻辑开发之前,就应该定义好自己的异常处理流程。

1. 异常处理流程概述

异常处理的对象分为两类:

  1. 错误的请求:程序处理前就已经发生,如RequestMapping中不存在该请求的URL以及其他的一些HTTP错误。
  2. 程序中的错误:各种异常,包括Runtime异常。

Springboot会统一处理第1种错误,由ErrorController捕获并进行处理,根据请求的Accpet字段返回错误页面或json数据。这里贴出Springboot自己的BasicErrorController实现:

@Controllerpublic class BasicErrorController implements ErrorController {    @Value("${error.path:/error}")    private String errorPath;    private final ErrorAttributes errorAttributes;    public BasicErrorController(ErrorAttributes errorAttributes) {        Assert.notNull(errorAttributes, "ErrorAttributes must not be null");        this.errorAttributes = errorAttributes;    }    public String getErrorPath() {        return this.errorPath;    }    @RequestMapping(        value = {"${error.path:/error}"},        produces = {"text/html"}    )    public ModelAndView errorHtml(HttpServletRequest request) {        return new ModelAndView("error", this.getErrorAttributes(request, false));    }    @RequestMapping({"${error.path:/error}"})    @ResponseBody    public ResponseEntity> error(HttpServletRequest request) {        Map body = this.getErrorAttributes(request, this.getTraceParameter(request));        HttpStatus status = this.getStatus(request);        return new ResponseEntity(body, status);    }    //……}

也可以自己写一个ErrorController返回自定义页面。

对于程序中发送的异常,可以手动进行捕获。如果没有手动捕获或有所遗漏,Springboot提供了@ExceptionHandler(value={})对某种类型的异常统一进行处理。通常,可以通过转发(forward)或重定向(redirect)方式将该异常转给自己定制的ExceptionController进行处理。ExceptionController可以根据请求类型返回错误页面或json数据。

2. 自定义RuntimeException

可以定制自己的Exception进行异常信息记录:

public abstract class BaseRuntimeException extends RuntimeException {    private static final long serialVersionUID = -1842796916322056555L;    public BaseRuntimeException() {        super();    }    public BaseRuntimeException(String message) {        super(message);    }    public BaseRuntimeException(String message, Throwable cause) {        super(message, cause);    }    /**     * 返回异常的错误码     */    public abstract Integer getErrorCode();    /**     * 返回异常的描述(不带应用前缀)     */    public abstract String getErrorMessage();    /**     * 返回异常的日志(带应用前缀)     */    public abstract String getErrorLog();}

然后再Service层定义该服务模块异常信息。

public class MiscServiceException extends BaseRuntimeException implements IReThrowException {    private static final long serialVersionUID = -1844670008823631700L;    private MiscErrorCode miscErrorCode = MiscErrorCode.SUCCESS;    private String errorLog = MiscErrorCode.SUCCESS.getDesc();    public MiscServiceException() {        super();    }    public MiscServiceException(String message, Throwable cause) {        super(message, cause);        this.errorLog = message;    }    public MiscServiceException(String message) {        super(message);        this.errorLog = message;    }    public MiscServiceException(String message, MiscErrorCode miscErrorCode) {        super(message);        this.miscErrorCode = miscErrorCode;        this.errorLog = message;    }    /**     * 返回异常的错误码.     * 方便日志查看追踪.     *     * @see BaseRuntimeException#getErrorCode()     */    @Override    public Integer getErrorCode() {        return miscErrorCode.getValue();    }    /**     * 返回异常的描述.     * 可直接用于前端错误提示.     *     * @see BaseRuntimeException#getErrorMessage()     */    @Override    public String getErrorMessage() {        return miscErrorCode.getDesc();    }    /**     * 返回异常的日志.     * 用于服务器日志打印.     *     * @see BaseRuntimeException#getErrorLog()     */    @Override    public String getErrorLog() {        return errorLog;    }}

在Service层生成异常对象并抛出。

if (…)    throw new MiscServiceException(“log message…”, MiscErrorCode.XXXERRIR);

在Manager层继续向上抛出。

public interface SomeService {    DTO someFunc() throws MiscServiceException {    //…}

在Controller层捕获异常,进行处理——返回相关页面。

try {//…} catch (MiscServiceException e) {log.error(e.getErrorLog());return ResponseView.fail(e.getErrorCode(), e.getErrorMessage());}

如此以来,代码中定义的异常和错误均可以捕捉。

由于BaseRuntimeException是一种RuntimeException,Mananger层声明方法是不加throws Exception也可以通过编译。小猿建议每一个Manager的方法都加上throws Exception声明。另外,BaseRuntimeException实际上也可以直接继承Exception,这样编译器会强制要求对其进行异常进行处理。

3. @ExceptionHandler

上述方案解决了一部分自定义异常。对于其他的自己未定义的Runtime Exception,例如Null Pointer Exception,Springboot提供了ExceptionHandler,用于捕获所有代码中没有主动catch的异常。通常,我们该异常转发(forward)或重定向(redirect)至某个自定义的Controller进行处理。

转发和重定向的效果不同:在浏览器端,转发的请求,页面不会跳转,URL不会改变;重定向的请求,URL会改变。重定向实际上是浏览器进行了两次请求。对于参数传递,两种方式采取的方法也不同。重定向方式可以使用RedirectAttributes传递参数,转发方式则将参数放置在Request的Attributes中。

转发:

@ControllerAdvicepublic class CustomExceptionHandler {    /**     * 将异常页转发到错误页     */    @ExceptionHandler(Exception.class)    public ModelAndView handleError(HttpServletRequest req, Exception ex) {        log.info("Exception e : " + ex, ex);        if (BaseRuntimeException.class.isAssignableFrom(ex.getClass())) {            BaseRuntimeException e = (BaseRuntimeException) ex;            req.setAttribute("code", e.getErrorCode());            req.setAttribute("message", e.getErrorMessage());        } else {            req.setAttribute("code", FrontCode.OTHER_ERROR.getCode());            req.setAttribute("message", FrontCode.OTHER_ERROR.getDesc());        }        return new ModelAndView("forward:/exception");    }}

重定向:

/** * 将异常页使用重定向方式到错误页 * * @param req * @param ex * @param mode * @return */@ExceptionHandler(Exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex ,RedirectAttributes mode) {     log.info("Exception e : " + ex,ex);     ModelAndView mav = new ModelAndView();     if (BaseRuntimeException.class.isAssignableFrom(ex.getClass())) {     BaseRuntimeException e = (BaseRuntimeException) ex;     mode.addAttribute("code",e.getErrorCode());     mode.addAttribute("message",e.getErrorMessage());     }     else {     mode.addAttribute("code", FrontCode.OTHER_ERROR.getCode());     mode.addAttribute("message", FrontCode.OTHER_ERROR.getDesc());     }     return new ModelAndView("redirect:/exception");}

4. ErrorController

在下文贴出的示例中,我们将异常处理的Controller也放在ErrorController中。其中,errorHtml方法同于对没有经过Controller层的错误进行处理,返回自定义错误页;exception和exceptionHtml方法负责接收ExceptionHandler转发或重定向的异常处理流,根据produces的类型是”json/application”还是“text/html”,分别返回json和错误页面。

@Controllerpublic class CommonErrorController implements ErrorController {    @Autowired    private UserSecurityHelper userSecurityHelper;    private static final String ERROR_PATH = "error";    private static final String EXCEPTION_PATH = "exception";    @RequestMapping(value = ERROR_PATH)    public ModelAndView errorHtml(HttpServletRequest request) {        ModelAndView modelAndView = new ModelAndView("/error/error");        Object statusCode = request.getAttribute("javax.servlet.error.status_code");        //当请求的错误类型非404、403、402、401时,返回500的错误页面        if (statusCode == null                || (!statusCode.equals(HttpStatus.NOT_FOUND.value())                && !statusCode.equals(HttpStatus.UNAUTHORIZED.value())                && !statusCode.equals(HttpStatus.PAYMENT_REQUIRED.value()) && !statusCode                .equals(HttpStatus.FORBIDDEN.value()))) {            statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();        }        modelAndView.addObject("code", statusCode);        modelAndView.addObject("message", "你很神,找到了不存在的页面。");        return modelAndView;    }    /*    * 使用forward转发.    */    @RequestMapping(value = EXCEPTION_PATH, produces = "application/json")    @ResponseBody    public ResponseEntity exception(HttpServletRequest request) {        Integer code = (Integer) request.getAttribute("code");        String message = (String) request.getAttribute("message");        return ResponseView.fail(code, message);    }    @RequestMapping(value = EXCEPTION_PATH, produces = {"text/html"})    public ModelAndView exceptionHtml(HttpServletRequest request) {        EduWebUser eduWebUser = userSecurityHelper.getEduWebUser();        ModelAndView mav = new ModelAndView("/error/error");        mav.addObject("code", (Integer) request.getAttribute("code"));        mav.addObject("message", (String) request.getAttribute("message"));        mav.addObject("webUser", eduWebUser);        return mav;    }    @Override    public String getErrorPath() {        return ERROR_PATH;    }}

如果使用Redirect跳转,ErrorController中接收参数的相应代码要随之改变。

/*使用Redirect跳转*/@RequestMapping(value = EXCEPTION_PATH, produces = "application/json")@ResponseBodypublic ResponseEntity exception(HttpServletRequest request , @RequestParam(required = false) String message,                                @RequestParam(required = false) Integer code) {    Map body = new HashMap<>(4);    body.put("code", code);    body.put("message", message);    return new ResponseEntity(body, HttpStatus.OK);}@RequestMapping(value = EXCEPTION_PATH, produces = { "text/html"})public ModelAndView exceptionHtml(HttpServletRequest request , @RequestParam(required = false) String message,                                  @RequestParam(required = false) Integer code) {    ModelAndView mav = new ModelAndView("/error/error");    EduWebUser eduWebUser = userSecurityHelper.getEduWebUser();    mav.addObject("code", code);    mav.addObject("message", message);    mav.addObject("webUser", eduWebUser);    return mav;}

5. 测试

我们定义了专门用于测试的Service和Controller。其中,throw测试程序中代码捕获异常,silent测试由ExceptionHandler捕获的异常。

public interface ExceptionService {    public void testThrowException() throws MiscServiceException;    public void testSilentException();}@Service("exceptionService")public class ExceptionServiceImpl implements ExceptionService {    @Override    public void testThrowException() throws MiscServiceException {        throw new ForumException("Log Message");    }    @Override    public void testSilentException() {        throw new ForumException("Log Message");    }}@RestController@RequestMapping("/exception")public class ExceptionController {    @Resource    private ExceptionService exceptionService;    @RequestMapping("/throw")    public ResponseEntity testThrow() {        try {            exceptionService.testThrowException();            return ResponseView.success(null);        } catch (MiscServiceException e) {            e.printStackTrace();            return ResponseView.fail(e.getErrorCode(), e.getErrorMessage());        }    }    @RequestMapping("/silent")    public ResponseEntity testSilent() {        exceptionService.testSilentException();        return ResponseView.success(null);    }}

测试记录如下。

代码主动捕获异常:

Springboot通过forward方式统一捕获异常:

Springboot通过redirect方式统一捕获异常:

Springboot统一捕获异常,返回json:

对于Controller之前发生的错误和异常,返回自定义页面:

如果对我的更新内容很感兴趣,希望可以得到您的一个点赞和关注,这将是对我最大的鼓励和支持,感谢~不论多与少,我都会坚持更新。另外,私聊我【Java学习资料】可以收获互联网大厂Java面经和Java最新最全资料~帮助你早日拿到互联网大厂的offer~

springboot转发http请求_网易后端实习生分享:Springboot异常和错误处理规范相关推荐

  1. springboot转发http请求_如何实现Http请求报头的自动转发

    HeaderForwarder组件不仅能够从当前接收请求提取指定的HTTP报头,并自动将其添加到任何一个通过HttpClient发出的请求中,它同时也提供了一种基于Context/ContextSco ...

  2. Nginx转发https请求访问http后端接口

    Nginx转发https请求 问题描述 前后端分离项目,前端使用Nginx部署,后端是Spring Boot项目,使用tomcat部署. Nginx配置了SSL,并且前端项目需要https协议访问.后 ...

  3. springboot转发http请求_Spring Boot2 系列教程(八)Spring Boot 中配置 Https

    https 现在已经越来越普及了,特别是做一些小程序或者公众号开发的时候,https 基本上都是刚需了. 不过一个 https 证书还是挺费钱的,个人开发者可以在各个云服务提供商那里申请一个免费的证书 ...

  4. springboot超详细教程_全网最细致的SpringBoot实战教程,超适合新手小白入坑学习...

    一.Spring Boot 入门 Spring Boot 来简化Spring应用开发的一个框架,约定大于配置 Spring Boot 的底层用的就是Spring 访问官网:spring.io 再点击p ...

  5. springboot集成钉钉_【程序源代码】springboot集成钉钉机器人

    "关键字: 集成 机器人" 正文:springboot集成钉钉机器人 01 -框架 springboot集成钉钉机器人实现消息通知中间件.项目基于钉钉开放平台-群机器人API的基础 ...

  6. python 异常处理模块_扩展Python模块系列(五)----异常和错误处理

    在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题.重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_ ...

  7. jmc线程转储_查找线程转储中的异常和错误

    jmc线程转储 线程转储是解决生产问题/调试生产问题的重要工件. 在像过去我们已经讨论了几种有效的线程转储故障模式: 堵车 , 跑步机 , RSI , 一个 LL条条大路通罗马 .......... ...

  8. nginx请求转发被拒绝_解决nginx反向代理proxy不能转发header报头

    做了一个德国高防plesk卖虚拟主机,奈何地理位置太过于遥远,控制台使用上速度难以接受.用户站点可以使用cloudflare等等的加速手段,控制台能否也这么干呢?理论是完全可以的,那么时间上手看吧.安 ...

  9. getaway网关转发去前缀_为什么微服务一定要有网关?

    作者:赵计刚 https://www.cnblogs.com/java-zhao/p/6716059.html 1.什么是服务网关 服务网关 = 路由转发 + 过滤器 1.路由转发:接收一切外界请求, ...

最新文章

  1. jenssen不等式的证明
  2. “.中国”域名总量跌至25.9万个:9月份净减2,249个
  3. CTFshow php特性 web113
  4. LVS(12)——sh
  5. wordpress进阶教程(十九):创建自定义的找回密码页面
  6. Mac OS下使用VS Code对C++程序进行debug的配置
  7. matlab中的g2(t)是什么,matlab实验1-8带答案,,
  8. Ansible Tower - 使用入门 4 - 用 Workflow 执行模板
  9. 消息中间件学习总结(18)——MQ常见面试题总结
  10. jquery子元素过滤选择器
  11. Eclipse中的m2e不支持如何修复maven-dependency-plugin(目标为“ copy-dependencies”,“ unpack”)
  12. html英文特殊字体代码,字体_中英文字体等(示例代码)
  13. 2022黑马程序员-前端学习第一阶段(Day02-HTML基础)
  14. CTFHUB刷题 密码口令/默认口令
  15. 已知三角函数值用计算机如何求角度,【已知三角函数值求角度】第一册已知三角函数值求角...
  16. Python Django Web开发之表单
  17. 三种存储类型:块存储、文件存储、对象存储
  18. 前端和后端到底有什么区别?待遇和前景如何?
  19. Ubuntu18.04之lightdm取代gdm
  20. Source Insight乱码解决方案

热门文章

  1. Leetcode--17.电话号码的字母组合
  2. Java——String类的方法
  3. 从零开始用python处理excel数据_Python对Excel的操作
  4. go 微服务框架_清晰架构(Clean Architecture)的Go微服务
  5. php文件上传到虚拟主机,php源码上传到虚拟主机(php源码上传到服务器)
  6. VC++6.0怎么打开工程
  7. Python获得一篇文档的不重复词列表并创建词向量
  8. 安卓逆向_19( 二 ) --- APK保护策略【重新签名后安装打开失败 --- 书旗小说.apk、浦发银行.apk的过签名校验【so 文件修改保存】】
  9. Flask --- 框架快速入门
  10. Scrapy-Link Extractors(链接提取器)