SpringBoot错误处理机制(ControllerAdvice+ExceptionHandler自定义错误处理、默认机制源码分析、错误处理实战)

1. SpringBoot自己对错误进行处理

1.1 给一个Controller进行错误处理

使用@ExceptionHandler,处理一个@Contoller的异常

package com.hh.springboot3test.controller;

import org.springframework.web.bind.annotation.*;




@RestController
public class ControllerTest {

    /*
    对整个RestController的异常进行处理
     */
    @ResponseBody       // 将异常消息返回给客户端
    @ExceptionHandler(Exception.class)      // 标识能处理的异常
    public String handleException(Exception e) {    // 接收异常
        return "出错了, 原因: " + e.getMessage();
    }

}

1.2 使用ControllerAdvice统一处理错误

使用@ControllerAdvice处理所有@Controller发生的错误。优先级比@Controller类的低

package com.hh.springboot3test.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


// 标明这个类是集中处理所有@Controller发生的错误
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {

        return "出错了, 统一处理,原因: " + e.getMessage();
    }
}

2. 默认机制源码解析

如果我们没有进行自定义的错误处理,springBoot提供默认的错误处理机制。错误处理的自动配置都在ErrorMvcAutoConfiguration中。整个处理流程如下:

默认机制

如果SpringMVC处理不了,则将错误请求发送给server.error.path=/error参数配置的路径,如果没有配置参数,则默认是/error。由ErrorMvcAutoConfiguration添加的BasicErrorController组件进行处理。如下所示:

......省略部分......
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
......省略部分......
}

SpringBoot会自适应处理错误,浏览器优先响应页面或移动端优先响应JSON数据。和内容协商一样,header头的accept参数设置application/xml,则以XML格式返回数据。BasicErrorController部分代码如下所示:

    @RequestMapping(
        produces = {"text/html"}    // 返回HTML
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping    // 返回ResponseEntity, 可以是JSON
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

我们先说HTML的错误处理。在errorHtml方法。错误页面先调用resolveErrorView,通过DefaultErrorViewResolver解析的

		// 解析错误的自定义视图地址
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);

ErrorMvcAutoConfiguration给容器专门添加了一个错误视图解析器DefaultErrorViewResolver

        @Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean({ErrorViewResolver.class})
        DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resources);
        }

SpringBoot的DefaultErrorViewResolver,提供了解析自定义错误页的默认规则

    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        // 先进行精确码处理
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        // 精确码处理不了,再进行4xx、5xx处理
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        // 先在classpath:templates/error路径下查找
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        // 如果classpath:templates/error没找到,则调用resolveResource方法,从静态路径下面的error文件夹下去找
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        String[] var3 = this.resources.getStaticLocations();
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];

            try {
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return new ModelAndView(new HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
            }
        }

        return null;
    }

如果errorHtml方法,调用resolveErrorView处理不了。则在errorHtml方法访问classpath:templates/error.html

        return modelAndView != null ? modelAndView : new ModelAndView("error", model);

如果classpath:templates/error.html都未提供。ErrorMvcAutoConfiguration给容器添加一个默认名为error的view。提供了默认白板功能

        @Bean(
            name = {"error"}
        )
        @ConditionalOnMissingBean(
            name = {"error"}
        )
        public View defaultErrorView() {
            return this.defaultErrorView;
        }

再说移动端的错误处理。DefaultErrorAttributes封装了JSON格式的错误信息

    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }

规则总结:

  1. 解析一个错误页
    1. 如果发生了403、404、500、503这些错误
      1. 如果有模板引擎,默认在classpath:/templates/error/精确码.html。如403.html文件
      2. 如果没有模板引擎,在静态资源文件夹下的error文件夹下找精确码.html
    2. 如果匹配不到精确码.html这些精确的错误页,就去找5xx.html,4xx.html模糊匹配
      1. 如果有模板引擎,默认在classpath:/templates/error/5xx.html。如5xx.html文件
      2. 如果没有模板引擎,在静态资源文件夹下的error文件夹下找 5xx.html
  2. 如果上面处理不了,则从模板引擎路径classpath:templates下找error.html页面。如果error.html页面都没有,则使用springboot提供的默认白板页面

3. 错误处理机制实战

  • 前后分离
    • 后台发生的所有错误,@ControllerAdvice + @ExceptionHandler进行统一异常处理,将错误数据返回给前端
  • 服务端页面渲染
    • 不可预知的一些,HTTP码表示的服务器或客户端错误
      • 给classpath:/templates/error/下面,放常用精确的错误码页面。如500.html、404.html
      • 给classpath:/templates/error/下面,放通用模糊匹配的错误码页面。如5xx.html、4xx.html
    • 发生业务错误
      • 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页
      • 通用业务,用classpath:/templates/error.html页面,显示错误信息

model中会默认存放一些数据。如下所示。我们自己也可以向model添加数据,然后再错误页面渲染的时候进行使用
model

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值