欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot-错误处理及自定义全局异常处理机制

程序员文章站 2022-05-03 19:18:31
...

正常的Web应用开发时,需要考虑到应用运行发生异常时或出现错误时如何来被处理,例如捕获必要的异常信息,记录日志方便日后排错,友好的用户响应输出等等。

当然应用发生错误,有可能是应用自身的问题,也有可能是客户端操作的问题。

Spring Boot默认提供了一种错误处理机制。

默认错误处理机制

默认情况下,Spring Boot为两种情况提供了不同的响应方式。

一种是浏览器客户端访问应用发生错误时,一般情况下浏览器默认发送的请求头中Accept: text/html(当然你更改了就另当别论了),所以Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”。

Spring Boot-错误处理及自定义全局异常处理机制

另一种是机器客户端访问应用发送错误时,Spring Boot会响应Json格式内容。这种情况更常见于利用第三方的Http工具请求接口时。

Spring Boot-错误处理及自定义全局异常处理机制

如果看过源码,会发现两种方式输出用到的内容项是一样的,只不过第一种方式用html格式显示,第二种方式使用了Json格式。

Spring Boot提供这个错误处理机制靠自动配置的BasicErrorController类。如果好奇Spring Boot是如何实现这种机制的。可以参看下BasicErrorController源码以及SpringMVC请求映射匹配规则,点这里

自定义多种错误页面

上述的默认错误处理机制是一种通用的做法,你可能更期望细化一些处理,对于某些错误你可能想特殊对待。Spring Boot提供了一种方式,笔者认为这种方式是“容器级别”的操作。如下:

@Configuration
public class ContainerConfig {
    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer(){
        return new MyCustomizer();
    }

    private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500"));
        }
    }
}

笔者新增了一个配置类,里面定义了一个bean,主要目的在于针对响应码为500的错误,采用笔者自定义的方式处理。如下:

@RestController
public class ExceptionController {
    @RequestMapping("/exception")
    public void catchException() {
        throw new RuntimeException("error occur");
    }

    @RequestMapping("/500")
    public String showServerError() {
        return "server error";
    }
}

那么浏览器中结果如下:

Spring Boot-错误处理及自定义全局异常处理机制

很明显这种方式依赖于响应状态码进行定制。看到这里你应该会眼熟,还记得web.xml文件里的配置吗?

<error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/error/500</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/error/500</location>
</error-page>

<error-page>
    <error-code>401</error-code>
    <location>/error/401</location>
</error-page>

<error-page>
    <error-code>403</error-code>
    <location>/error/403</location>
</error-page>

笔者认为,Spring Boot的默认错误处理机制,包括自定义的错误页面,与原来的web.xml中的error-page配置是相通的,只不过Spring Boot应用默认使用内嵌Servlet容器,web.xml不见了而已。查看源码你会发现,Spring Boot在应用发生错误时会转向"/error"请求,即交由BasicErrorController处理。

覆盖默认的错误处理方式

默认错误处理机制的响应内容格式不一定是你相中的。理由可能如下:

  1. “Whitelabel Error Page”页面的样式太单调,用户体验不好。
  2. Json格式的结果字符串不统一,与你配合的前端人员更希望统一格式,好做统一的显示处理。比如笔者之前与前端人员配合时统一指定响应结果格式为“{status:true,msg:"xxx",data:{xxx}}”,但Spring Boot的Json格式是{status:500,message:"error occur",path:"/exception"......}。

那么你可能更期望可以修改默认的处理方式,改变响应内容格式。Spring Boot开发指南上给出了几种方法。

  1. 自定义一个bean,实现ErrorController接口,那么默认的错误处理机制将不再生效。
  2. 自定义一个bean,继承BasicErrorController类,使用一部分现成的功能,自己也可以添加新的public方法,使用@RequestMapping及其produces属性指定新的地址映射。
  3. 自定义一个ErrorAttribute类型的bean,那么还是默认的两种响应方式,只不过改变了内容项而已。

先说下第三种方法,其实查看BasicErrorController源码,响应结果不论是html还是json,内容源都是ErrorAttribute。第三种方法只能改变内容,却改变不了格式,特别是html页面的样式,笔者就不予考虑了。

其实指南上只是轻描淡写了几种方法,没有很好的示例,不知道是不是指南的作者认为Spring Boot默认的错误处理机制已经很适用了。无论第一种还是第二种办法,都需要你看下BasicErrorController的继承体系及实现,因为BasicErrorController也是实现了ErrorController。笔者也是着实费了一番功夫。

采用第一种方式你可以具有完全的控制权,你可以摒弃默认的“Whitelabel Error Page”,指定自己的视图及视图样式,你可以指定响应的Json格式内容等等,因为BasicErrorController不再起作用,可以参考这里

由于笔者参照了BasicErrorController的源码,感觉第二种方法可能更简便些,所以实现了第二种方法。第二种方法的思路其实就是你可以通过继承,利用BasicErrorController已有的功能,或者进行扩展。

那么如何覆盖默认的处理行为呢(虽然是自定义bean,但因为是继承,没有覆盖的话还是会采用默认的处理行为)?大致有两种思路。

  1. 按照指南上所述,你可以新建public方法,使用@RequestMapping及其produces属性,例如@RequestMapping(produces="application/json"),那么只要请求头中包含“Accept: application/json”,则会映射到你的方法进行处理。看到这儿你可能会疑惑,why?还是建议你先看下BasicErrorController源码
  2. 因为是继承,所以对BasicErrorController默认两种错误处理方式的方法进行override。

笔者希望完全覆盖及可控,所以选择了第二种思路。笔者自定义了MyErrorController,继承于BasicErrorController,注意一定要添加@Controller,不然Spring无法感知自定义的bean,继承于BasicErrorController还是会起作用!具体实现可猛戳这里

        demo具体实现案例

其他

Spring Boot提供的ErrorController是一种全局性的容错机制。你还可以使用SpringMVC提供的@ControllerAdvice。

如字面意思,@ControllerAdvice是切面技术的应用,允许你对Controller中抛出的某个或某些异常进行捕获并响应输出。用法如下:

@ControllerAdvice
public class DefaultExceptionHandler  {

	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionHandler.class); //日志记录器

	@ExceptionHandler({MissingServletRequestParameterException.class, TypeMismatchException.class, IllegalArgumentException.class, IllegalStateException.class})
	@ResponseStatus(value = HttpStatus.BAD_REQUEST)
	@ResponseBody
	public JsonResult conversionErrorHandler(Exception ex) {
        //记录日志
        LOGGER.error("参数异常捕获", ex);

        return new JsonResult(false, ErrorCode2Msg.getMessage(10004));
	}
}

笔者以往的开发习惯,使用@ControllerAdvice捕获应用级别的异常,使用web.xml中的error-page配置处理容器级别的报错。假设定义的过滤器抛出的异常,@ControllerAdvice是无法处理的(假设定义的过滤器抛出的异常,@ControllerAdvice是无法处理的

改用Spring Boot后,@ControllerAdvice没有捕获的异常,ErrorController会帮你“捡起来”。

转载自:https://blog.csdn.net/lj402159806/article/details/54694580