Spring-boot 异常处理

项目难免会出现系统抛出异常或者404 。 直接把错误信息反馈给用户不太好。所以要统一处理异常并返回直观的提示。
【以下展示了常见的实现方法,应该算比较完整的】

一、统一返回指定错误页面 【4种方法】
  • 方法1:使用继承 ErrorController , (注:【能处理 404、500等错误码】这个也会处理到 jscss 等静态资源)
    1. 后台抛出的异常,或者访问空的url都能处理。
package com.demo.controller;

import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;

@Controller
public class ExceptionController implements ErrorController {

    @RequestMapping("error")
    public String handleError(HttpServletRequest request){
        HttpStatus status = this.getStatus(request);
        if(status.value() == 404){
            return "404"; // 返回404页面
        }else if(status.value() == 500){
            return "500"; // 返回 500 页面
        }
        return "error"; // 返回其它错误的页面
    }

    @Override
    public String getErrorPath() {
        return "error"; // 如果异常则请求到此url (这里是请求到/error),可以自定义
    }

    /**
     * 获取状态码
     * @param request
     * @return
     */
    protected HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        } else {
            try {
                return HttpStatus.valueOf(statusCode.intValue());
            } catch (Exception var4) {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }
        }
    }
}
  1. 其它的一些信息可以通过request.getAttribute("属性");获取,常见属性:
属性 描述
javax.servlet.error.status_code 该属性给出状态码,状态码可被存储,并在存储为 java.lang.Integer 数据类型后可被分析。
javax.servlet.error.exception_type 该属性给出异常类型的信息,异常类型可被存储,并在存储为 java.lang.Class 数据类型后可被分析。
javax.servlet.error.message 该属性给出确切错误消息的信息,信息可被存储,并在存储为 java.lang.String 数据类型后可被分析。
javax.servlet.error.request_uri 该属性给出有关 URL 调用 Servlet 的信息,信息可被存储,并在存储为 java.lang.String 数据类型后可被分析。
javax.servlet.error.exception 该属性给出异常产生的信息,信息可被存储,并在存储为 java.lang.Throwable 数据类型后可被分析。
javax.servlet.error.servlet_name 该属性给出 Servlet 的名称,名称可被存储,并在存储为 java.lang.String 数据类型后可被分析。
  1. 完整属性看下图:

    image.png

  2. 比如获取异常信息:

    /**
     * 获取异常信息 
     * @param request
     * @return
     */
    protected HttpStatus getException(HttpServletRequest request) {
        String e= (Integer)request.getAttribute("javax.servlet.error.exception"); // 主要是这句
    }
  • 方法2:使用 @ControllerAdvice@ExceptionHandler注解 【只能处理 500等服务错误码,无法处理404 , 并且无法处理filter抛出的异常】
package com.demo.config;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class) // 这里是要处理的异常,可以换成其它的
    public String defaultErrorHandler(HttpServletRequest request , HttpServletResponse response, Throwable e){
        // 这里可以做一些其它的操作。 可以获取完整的异常信息
        return "500" ; // 返回的字符串会匹配上对应页面 。500 对应 500.html
    }
}

- 附上官方教程 Error Handling (打开网页后请等待加载完成,会自动跳转到 Error Handling )

  • 方法3:最简单的方法 ,在/resources/public/error下错误页面 只适用于返回错误页面,可以处理404、500等错误
    1. /resources/public/error/404.html自动展示404错误, /resources/public/error/500.html自动展示500错误页面。可以自已添加其它错误码的页面
      image.png

- 附上官方教程 Custom Error Pages ,(打开网页后请等待加载完成,会自动跳转到 Custom Error Pages )

  • 方法4:通过配置EmbeddedServletContainerCustomizer来实现 【能处理 404、500等错误码】这个也会处理到 jscss 等静态资源
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResumeApplication.class, args);
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return container -> {
            // container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error"));  这里还可以映射到controller上去处理
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html")); // 也可以映射到指定html文件上
            container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html")); 
            // 这里可以继续添加其它错误页面
        };
    }
}

二、统一返回 json ,针对 REST 风格的接口 【2种方法】
  • 方法1:使用继承 ErrorController , (注:这个也会处理到 jscss 等静态资源)
    和上面的方式差不多类似 ,使用@RestController 而不是 @Controller 注解
package com.demo.controller;

import com.veslay.resume.domain.ArticleContent;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController // 注意这里是RestController 而不是 Controller
public class ExceptionController implements ErrorController {

    @RequestMapping("error")
    // @ResponseBody   也可以使用这个注解
    public ArticleContent handleError(HttpServletRequest request){
        ArticleContent articleContent = new ArticleContent() ; // 这里可以是自定义的对象
        articleContent.setNickName("nickName") ;
        return articleContent; // 返回其它错误的页面
    }

    @Override
    public String getErrorPath() {
        return "error";
    }
}
  • 方法2:通过配置EmbeddedServletContainerCustomizer来实现 【能处理 404、500等错误码】这个也会处理到 jscss 等静态资源
package com.veslay.resume;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;

@SpringBootApplication
public class ResumeApplication {

    public static void main(String[] args) {
        SpringApplication.run(ResumeApplication.class, args);
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return container -> {
             // 这里的 /404 和 /500 是映射到controller的接口,接口返回json数据则ok .
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404"));
            container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500")); 
            // 这里可以继续添加其它错误页面
          };
    }
}
  • 方法3:使用 @RestControllerAdvice@ExceptionHandler注解 【只能处理 500等服务错误码,无法处理404】
package com.demo.config;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class) // 这里是要处理的异常,可以换成其它的
    public ResponseModel defaultErrorHandler(HttpServletRequest request , HttpServletResponse response, Throwable e){
        // 这里可以做一些其它的操作。 可以获取完整的异常信息
        //  ResponseModel 对象是自定义的,这个对象会自动转成json格式的
        return ResponseModel.ERROR(e.getMessage()) ; /
    }
}
  • ResponseModel 对象是自定义的,这个对象会自动转成json格式的
{
    "timestamp": "2018-07-13 15:11:42",
    "status": 400,
    "message": "数据无法获取",
    "data": null
}
  • 使用@RestControllerAdvice时如果后台报错org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation,前端状态码为406 ,如下处理:
@ExceptionHandler(Exception.class) // 这里是要处理的异常,可以换成其它的
    public ResponseModel defaultErrorHandler(HttpServletRequest request , HttpServletResponse response, Throwable e){
// 增加这个,移除请求头中的 text/plain
        request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        return ResponseModel.ERROR(e.getMessage()) ; /
    }

这个错误场景是:前端是react , post请求上传文件,并且在url路径中还传了参数?b=xxx&a=a ,接收后端返回json, 后端解析上传的文件时异常,统一异常处理的类打算把500的错误码改成200并返回ResponseModel对象,然后就出现了这个错误





- 最最重要的一点来了,点晴之笔,网上的类似教程就是没有说明这一点,所以导致很多都没有成功实现功能。 以上的每个方法都要加下面的核心配置 , 配置如下

  • application.yml
spring:
  mvc:
    view:
      suffix: .html
      prefix: /
  • application.properties
spring.mvc.view.prefix= /
spring.mvc.view.suffix= .html

注意:在这里最好要打印一下异常的堆栈信息,不然可能前端收到500的异常响应,而后却没有日志!!!!


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容