Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Springboot异常处理只会@ControllerAdvice+@ExceptionHandler?还远远不够!

Springboot异常处理只会@ControllerAdvice+@ExceptionHandler?还远远不够!

作者头像
java思维导图
发布于 2019-09-19 10:59:31
发布于 2019-09-19 10:59:31
2.4K00
代码可运行
举报
文章被收录于专栏:java思维导图java思维导图
运行总次数:0
代码可运行

当系统出现异常时候,或404,或500,默认返回的错误页面通常非常简陋,用户也看不懂,这时候我们想通过一些手段,提示用户访问的资源不存在,或者请稍后再试。

同时有个统一的异常处理机制可以提高我们系统的健壮性,微服务化之后系统之间的调用结果会影响到整个服务的可用性。如果被调用方出现异常没有返回统一的异常处理结果,很容易会调用方疑惑,然后滚大整个异常,这时候你看到整个服务之间都在报错,这不是我们想看到的~

那么基于springboot,我们有多少种异常处理方式呢?

静态处理

这是一种比较偷懒也是最简单的处理方式,直接放置一个静态的页面。我们静态看到有些项目直接就返回一个大大的404图片作为异常的处理显示,其实就是这里说到的静态处理方式。

我们来看下错误页面的存放位置:

可以看到,我是存放在了static目录的error文件夹下,新建了一个404.html用于处理404错误。既然是静态页面,那么就不能使用动态渲染,所以通常静态的异常页面都会写得比较死,要么就直接就是一个404图片。

静态页面中如果写了中文,这是显示的内容容易乱码,我们只需在配置文件application.properties中添加以下encoding代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
spring.http.encoding.force=true

我们先来访问一个不存在的路径http://localhost:8080/xxxx,看下效果:

  • 未处理前:
  • 静态处理后:

我们的404.html页面起作用啦,如果不存在404.html,或者出现401异常的时候,系统就会自动匹配到4xx.html页面,所以这个4xx相当于可以通配处理所有的客户端错误:4xx。类似的500.html和5xx.html处理服务器错误:5xx。

好,上面的静态处理异常我们已经可以懂了,那么你知道它的原理吗?

其实在springboot项目启动的时候,会去加载异常处理的默认配置ErrorMvcAutoConfiguration,而在ErrorMvcAutoConfiguration里面,有个默认的异常处理控制器BasicErrorController(org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController),我们在这构造方法中打个端点,可以看到异常处理器errorViewResolvers的resourceProperties中就默认初始化好了所有可以存放静态异常页面的地方。

然后你再把端点打在ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)方法上,你就会清晰看到,其实springboot项目会循环搜索这4个位置的文件夹,看时候有404.html页面,如果有就直接返回,没有就返回异常的默认处理页面。

总结一下:静态处理的错误页面可以存放4个位置,分别按先后顺序搜索

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/META-INF/resources/error/404.html -> /resources/error/404.html 
-> /static/error/404.html -> /public/error/404.html

当找不到精确匹配404.html的时候,就会找通配的4xx.html。

ok,静态处理就先讲到这里~

动态处理

刚才我们说了一种静态处理异常的方式,但是缺点很明显,不能在静态页面中动态渲染数据啊!这无疑是比较致命的,有什么办法让页面能动态处理呢?

这里给大家介绍4种方式

  • 直接在templates下写error页面,如果freemaker的error.ftl
  • 重写ErrorController,覆盖BasicErrorController
  • 继承ErrorPageRegistrar,重写registerErrorPages方法
  • @ControllerAdvice+@ExceptionHandler组合
1、直接写error.ftl

这个其实和静态处理中一样,页面处理器在静态资源中找不到对应的页面之后就会直接去templates下找view直接返回,默认的名字就叫做error,所以当我们直接在tempates下写error.ftl时候,我们就可以直接展示动态错误处理页面了。

但是这样我们直接返回页面,没办法自己控制错误的业务逻辑处理,所以,只有当我们出现错误之后没有相关的处理,我们才这样去展示。

2、重写ErrorController

在静态处理代码分析的时候我们说到了项目启动时候就会自动加载默认异常处理配置ErrorMvcAutoConfiguration,会默认加载BasicErrorController作为异常处理的控制器。那么我们想要自己定义处理逻辑的话,我们就直接覆盖掉BasicErrorController,然后重写一个就好了。

我们先来看下ErrorController长什么样子的

  • org.springframework.boot.web.servlet.error.ErrorController
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@FunctionalInterface
public interface ErrorController {
    String getErrorPath();
}

getErrorPath()其实表示的就是出现异常之后应该调用的链接,所以当我们如果返回的链接是/error时候,我们应该新建一个controller处理方法对应/error链接。

总结上面的逻辑,我写了如下代码:

  • 1、实现ErrorController接口
  • 2、重写getErrorPath()方法
  • 3、定义web页面异常处理和异步异常处理方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
@Controller
public class IErrorController implements ErrorController {

    private final static String ERROR_PATH = "/error";

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

    @RequestMapping(value = ERROR_PATH, produces = "text/html")
    public String errorView(HttpServletRequest request) {

        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        Object errorMess = request.getAttribute(RequestDispatcher.ERROR_MESSAGE);

        request.setAttribute("message", "这是错误提示!!" + errorMess);

        if (status != null) {
            Integer statusCode = Integer.valueOf(status.toString());

            if (statusCode == HttpStatus.NOT_FOUND.value()) {
                return "/common/404";
            } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
                return "/common/500";
            }
        }
        return "/common/error";
    }

    @ResponseBody
    @RequestMapping(value = ERROR_PATH)
    public Object errorJson() {
        return "这里放回json数据~";
    }
}

在templates/common文件夹下建好对应的页面:

渲染结果:

3、继承ErrorPageRegistrar

ErrorPageRegistrar是一个错误页面的注册器,在ErrorMvcAutoConfiguration中我们依然可以找到对应的源码代码,它默认帮我们写了一个ErrorPageCustomizer用于处理注册的错误页面,我们可以看到启动时候,会默认先把/error页面注册进去。

接下来,我们来重写一个ErrorPageRegistrar,先来看下接口的源代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ErrorPageRegistrar {
   void registerErrorPages(ErrorPageRegistry registry);
}

只有一个方法registerErrorPages,参数是错误页面注册中心ErrorPageRegistry。接下来我们自己来定义一个类。

  • com.example.config.MyErrorPageRegistrar
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class MyErrorPageRegistrar implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {

        ErrorPage page404 = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
        ErrorPage page500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500");

        System.out.println("初始化了!");
        errorPageRegistry.addErrorPages(page404, page500);
    }
}

我们定义了两个错误页面,一个ErrorPage 404,还要ErrorPage 500,分别链接到/404,还有/500,所以需要给controller定义两个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Controller
public class IndexController {

    @RequestMapping("/404")
    public String error404(HttpServletRequest request) {
        request.setAttribute("message", "ErrorPageRegistrar的404页面");
        return "common/404";
    }

    @RequestMapping("/500")
    public String error500() {
        return "common/500";
    }

}

这样的话,就会分别链接到IndexController的方法里面,然后再跳转到对应的页面中。这就实现了错误页面的动态处理了。

我试了一下,当implements ErrorControllerimplements ErrorPageRegistrar两种方式同时存在的时候,优先通过ErrorPageRegistrar来处理异常。这点得牢记哈。

4、@ControllerAdvice+@ExceptionHandler组合

接下来再聊聊一个人人都应懂得@ControllerAdvice+@ExceptionHandler组合。

其实不一定需要组合来一起用,当我们需要在某个特定控制器里面处理特定异常时候,我们的@ExceptionHandler可以直接写在controller中,这样的话@ExceptionHandler就只能处理这个单个controller的异常。

那有时候我们想全局处理所有的控制器的异常,于是就有了@ControllerAdvice,它会控制器增强,会应用到所有的controller上,这样就实现了我们想要的全局异常处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) {

        log.error("------------------>捕捉到全局异常", e);

        if (req.getHeader("accept").contains("application/json")  || (req.getHeader("X-Requested-With")!= null
                && req.getHeader("X-Requested-With").contains("XMLHttpRequest") )) {
            try {
                System.out.println(e.getMessage());
                Result result = Result.fail(e.getMessage(), "some error data");

                resp.setCharacterEncoding("utf-8");
                PrintWriter writer = resp.getWriter();
                writer.write(JSONUtil.toJsonStr(result));
                writer.flush();
            } catch (IOException i) {
                i.printStackTrace();
            }
            return null;
        }

        if(e instanceof HwException) {
            //...
        }

        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("message", e.getMessage());
        mav.addObject("url", req.getRequestURL());
        mav.setViewName("error");
        return mav;
    }

}

当然了,这样的@ExceptionHandler(value = Exception.class)比价偷懒,你完全可以给value赋不同的Exception,然后针对不同的Exception类型做不同的处理。

下面是renren-fast项目的全局异常处理,我们来学习学习:

  • https://gitee.com/renrenio/renren-fast.git
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@RestControllerAdvice
public class RRExceptionHandler {
   private Logger logger = LoggerFactory.getLogger(getClass());

   /**
    * 处理自定义异常
    */
   @ExceptionHandler(RRException.class)
   public R handleRRException(RRException e){
      R r = new R();
      r.put("code", e.getCode());
      r.put("msg", e.getMessage());

      return r;
   }

   @ExceptionHandler(NoHandlerFoundException.class)
   public R handlerNoFoundException(Exception e) {
      logger.error(e.getMessage(), e);
      return R.error(404, "路径不存在,请检查路径是否正确");
   }

   @ExceptionHandler(DuplicateKeyException.class)
   public R handleDuplicateKeyException(DuplicateKeyException e){
      logger.error(e.getMessage(), e);
      return R.error("数据库中已存在该记录");
   }

   @ExceptionHandler(AuthorizationException.class)
   public R handleAuthorizationException(AuthorizationException e){
      logger.error(e.getMessage(), e);
      return R.error("没有权限,请联系管理员授权");
   }

   @ExceptionHandler(Exception.class)
   public R handleException(Exception e){
      logger.error(e.getMessage(), e);
      return R.error();
   }
}

你可以看到,他对异常的类型分得很多,很清楚,这样我们就可以让异常展示的结果越具体。

总结

好了,不容易呀,终于写完了,2个多小时,坚持原创的第二天(20190918),打卡打卡。希望你们会喜欢。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java思维导图 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」
异常处理在Java中是一种很常规的操作,在代码中我们常用的方法是try catch或者上抛异常。
鹿老师的Java笔记
2020/03/30
1.6K0
Springboot 系列(七)web 开发之异常错误处理机制剖析
相信大家在刚开始体验 Springboot 的时候一定会经常碰到这个页面,也就是访问一个不存在的页面的默认返回页面。
未读代码
2019/11/04
6130
微服务架构之Spring Boot(三十四)
Spring MVC使用 WebBindingInitializer 为特定请求初始化 WebDataBinder 。如果您创建自己的 ConfigurableWebBindingInitializer
用户1289394
2022/04/07
1.1K0
SpringBoot处理全局统一异常
0x01:使用@ControllerAdvice和@ExceptionHandler注解
BUG弄潮儿
2020/06/15
1.1K0
SpringBoot2-----异常处理
但是springmvc底层有basicErrorController专门来处理/error请求,如果用户没有自定义错误页,那么默认显示错误白页
大忽悠爱学习
2021/11/15
6690
SpringBoot错误信息处理机制及原理
ErrorMvcAutoConfiguration这个类存放了所有关于错误信息的自动配置。
石的三次方
2021/01/06
9580
深入Spring Boot (九):Web应用统一异常处理
默认情况下,Spring Boot为基于SpringMVC的Web应用提供了全局统一异常处理,本篇将深入介绍默认的统一异常处理及自定义异常处理,主要包含以下4部分内容: 默认异常处理; 覆盖默认异常处
JavaQ
2018/04/08
1.4K0
深入Spring Boot (九):Web应用统一异常处理
springboot 错误页面/消息配置增加异常信息输出
springboot中,请求错误,或者在controller抛出异常后将自动跳转到默认错误页面,或者返回默认错误消息
路过君
2021/10/15
1.6K0
Spring Boot错误处理
自定义一个类实现ErrorController,当系统发生404或者500错误的时候,就会自动进入到自定义的错误页面中,这要求在resources文件里面的templates文件内部建立一个error文件夹,里面放自定义错误页面的模板即可。当访问/error这个路径的时候,也会进入错误页面。
itlemon
2020/04/03
7770
Spring 全家桶之 Spring Boot 2.6.4(七)- Exception
使用IDEA创建一个工程spring-boot-exception,只需要添加基本的依赖即可
RiemannHypothesis
2022/09/26
1K0
Spring 全家桶之 Spring Boot 2.6.4(七)- Exception
SpringBoot 2.0 配置错误页面 原
为什么80%的码农都做不了架构师?>>> springboot 2.0 配置错误页面 @Configuration public class ErrorPageConfig implements
北漂的我
2019/05/29
2.5K0
SpringBoot 笔记(十):错误处理
SpringBoot 笔记 ( 十 ):错误处理 1)、SpringBoot默认的错误处理机制 默认效果: ​ 1)、浏览器,返回一个默认的错误页面 2)、如果是其他客户端,默认响应一个json数据 2)、自动配置原理 ​ 具体就是在 ErrorMvcAutoConfiguration,错误处理的自动配置。 给容器中添加了以下组件 1、ErrorPageCustomizer:规定错误页面 /error 12 @Value("${error.path:/error}
lwen
2018/06/19
8190
教你理清SpringBoot与SpringMVC的关系
spring boot就是一个大框架里面包含了许许多多的东西,其中spring就是最核心的内容之一,当然就包含spring mvc。spring mvc 是只是spring 处理web层请求的一个模块。因此他们的关系大概就是这样:spring mvc  < spring <springboot。
美的让人心动
2018/12/14
1.8K0
SpringBoot---错误处理机制
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error
大忽悠爱学习
2021/11/15
5090
知识点-Spring Boot 统一异常处理汇总
上面讲的是做页面开发的时候遇到的问题,还有一种情况就是用来开发Rest接口,当错误的时候我们希望返回给用户的是我们接口的标准格式,不是返回一段html代码。
猿天地
2018/07/25
9540
知识点-Spring Boot 统一异常处理汇总
掌握 Spring 之异常处理
这次我们学习 Spring 的异常处理,作为一个 Spring 为基础框架的 Web 程序,如果不对程序中出现的异常进行适当的处理比如异常信息友好化,记录异常日志等等,直接将异常信息返回给客户端展示给用户,对用户体验有不好的影响。所以本篇文章主要探讨通过 Spring 进行统一异常处理的几种方式实现,以更优雅的方式捕获程序发生的异常信息并进行适当的处理响应给客户端。
闻人的技术博客
2019/09/19
1.9K0
掌握 Spring 之异常处理
Spring 异常处理的各种姿势
统一的异常处理对于应用的重要性不言而喻。今天我们来介绍一下 Spring 如何来进行统一的 Rest 异常处理。同时我们也会简单比较一下它们之间的优劣。
码农小胖哥
2019/12/10
7040
SpringBoot全局异常处理
全局异常处理是个比较重要的功能,一般在项目里都会用到。 我大概把一次请求分成三个阶段,来分别进行全局的异常处理。 一:在进入Controller之前,譬如请求一个不存在的地址,404错误。 二:在执行@RequestMapping时,进入逻辑处理阶段前。譬如传的参数类型错误。 三:以上都正常时,在controller里执行逻辑代码时出的异常。譬如NullPointerException。 第一种情况:
天涯泪小武
2019/01/17
8850
SpringBoot自定义错误页面
然后你只需要写个controller拦截不同请求然后跳到不同的自定义错误页面即可,如下所示:
一写代码就开心
2020/11/20
1.4K0
Spring Boot中Web应用的统一异常处理
我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射: /error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。 选择一个之前实现过的Web应用(Chapter3-1-2)为基础,启动该应用,访问一个不存在的URL,或是修改处理内容,直接抛出异常,如: @RequestMapping("/hello")public String hello() throws Exception { throw ne
程序猿DD
2018/02/01
1.3K0
Spring Boot中Web应用的统一异常处理
相关推荐
SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验