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的异常响应,而后却没有日志!!!!


推荐阅读更多精彩内容