springboot 错误机制error

一。springboot默认处理异常机制
默认效果:
1)、浏览器,返回一个默认的错误页面 (通过浏览器的请求头来返回页面)
springboot 错误机制error_第1张图片
springboot 错误机制error_第2张图片
2)、如果是其他客户端,默认响应一个json数据
springboot 错误机制error_第3张图片
原理:主要给日容器中注册了以下组件:

  1. ErrorPageCustomizer 系统出现错误以后来到error请求进行处理;相当于(web.xml注册的错误页面规则)
  2. BasicErrorController 处理/error请求
  3. DefaultErrorViewResolver 默认的错误视图解析器
  4. DefaultErrorAttributes 错误信息
  5. defaultErrorView 默认错误视图

原理“

1.系统一旦出现错误,ErrorPageCustomize发送error请求进行处理;,(就如以前在web.xml文件中配置错误页面一样)
在这里插入图片描述
注册错误页面
在这里插入图片描述
路径为:
springboot 错误机制error_第4张图片
2.BasicErrorController 就会来处理上面的error请求。 可以通过配置server.error.path=来配置自定义的error请求路径

springboot 错误机制error_第5张图片
springboot 错误机制error_第6张图片
其内部的方法这两个 一个是用于返回页面视图 一个是返回json数据,如何区分返回页面视图还是json数据 是通过header的头信息来区别的。
springboot 错误机制error_第7张图片
响应页面:通过resolveErrorView方法获取响应的页面,
在这里插入图片描述
从modelAndView = resolver.resolveErrorView(request, status, model);获取页面
springboot 错误机制error_第8张图片
3.那么这些组件哪儿来呢,就是DefaultErrorViewResolver 得来。
springboot 错误机制error_第9张图片
调用了resolveErrorView()方法。
在error/状态码下面通过模板引擎寻找对应的页面

    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        //把状态码和model传过去获取视图
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);

        //上面没有获取到视图就使用把状态吗替换再再找,以4开头的替换为4xx,5开头替换为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) {
        //viewName传过来的是状态码,例:/error/404
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //模板引擎可以解析这个页面地址就用模板引擎解析
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

4.异常页面可以获取哪些信息呢?可以使用哪些属性呢?

再看一下BasicErrorController的errorHtml方法

    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);

        //model的数据
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

看一下调用的this.getErrorAttributes()方法

    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

再看 this.errorAttributes.getErrorAttributes()方法, this.errorAttributes是接口类型ErrorAttributes,实现类就一个DefaultErrorAttributes,看一下DefaultErrorAttributes的 getErrorAttributes()方法

    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

DefaultErrorAttributes

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

如何定制错误页面:

1)、如何定制错误的页面; 

1)、有模板引擎的情况下error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;

我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态 码.html);
页面能获取的信息;属性
timestamp:时间戳

status:状态码
error:错误提示

exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里**

2.0以后默认是不显示exception的,需要在配置文件中开启

server.error.include-exception=true 

原因:
springboot 错误机制error_第10张图片
在注册时
springboot 错误机制error_第11张图片
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找
静态资源文件夹下面就没法使用thymeleaf语法规则,则无法获取status这些属性
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面

3.自定义异常json数据

1.创建自定义的异常类:

package com.wcy.springboot.exception;

public class MyUserException extends RuntimeException {

    public MyUserException() {
        super("用户名出错了");//自定义异常类
    }
}

2.哪些地方捕获自定义异常:例子如下方username为123456则捕获异常

    @GetMapping("/hello")
    @ResponseBody
    public String hello(String username){
        if("123456".equals(username)){
            throw new MyUserException();//当用户名为123456则处理自定义异常
        }
        return "测试";
    }

3.自定义异常处理的控制类 有点像controller

package com.wcy.springboot.controller;

import com.wcy.springboot.exception.MyUserException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

//专门用于处理异常的控制器
@ControllerAdvice
public class ExceptionHandler  {

    //当捕获到自己自定义的异常时触发
    //这样不管事浏览器还是客户端访问都会返回json数据,显然是不符合
    @org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
    @ResponseBody
    public Map<String,Object> handlerException(Exception e){
        Map<String,Object> map=new HashMap<>();
        map.put("code","not username");
        map.put("msg","用户名没有找到");
        return map;
    }
}

  1. 显然上方的不满足我们的需求 我们需要让他自适应,浏览器异常访问我们自定义的页面,客服端访问显示json数据。
    /**
     * 1.可以通过请求转发 转发到error请求
     * 2.这样就会被springboot的BasicErrorController处理 它内部就有两个自适应的方法
     * 3.获取请求域中的状态码 我们需要自定义
     *      Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
    public String handlerException(Exception e, HttpServletRequest request){
        Map<String,Object> map=new HashMap<>();
        map.put("code","not username");
        map.put("msg","用户名没有找到");
        //传入我们的状态码 这样才能返回页面  默认返回200是成功 没法返回页面
        request.setAttribute("javax.servlet.error.status_code",500);
        request.setAttribute("etx",map);
        return "forward:/error";//转发到error请求
    }

所以我们的ExceptionHandler 中应该这样写 转发给BasicErrorController 中/error来处理,他内部就有两个自适应方法。
但是会存在问题是:页面会显示springboot内部的异常页面:原因是他会自动获取请求域中的状态码:显示的是200,所以没法跳转到我们自定义页面;

视图方法中获取请求码是通过:"javax.servlet.error.status_code"的属性解决的
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
    

虽然页面也可以访问了,但是我们没法把数据携带出去:
BasicErrorController的两个方法都是从getErrorAttributes()中获取参数的,getErrorAttributes()又是该AbstractErrorController类的方法。

  1. 方法一:完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;这样去重写那两个自适应方法就可以了,显然这样太复杂了,不推荐。
  2. return this.errorAttributes.getErrorAttribute的方法获取参数,
    他是又ErrorMvcAutoConfiguration(自动装配)中的errorAttributes方法来的,所以我们可以自定义组件 来继承DefaultErrorAttributes 类然后再重写方法就可以了
    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    }

自定义组件:

package com.wcy.springboot.component;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;
//是一个组件 让springboot识别这个类
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    //接受页面和json数据的属性
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //获取自定义异常处理类传过来的数据
        Map<String, Object> etx= (Map<String, Object>) webRequest.getAttribute("etx", 0);
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company","wcy");//自定义公司名称
        map.put("msg",etx);
        return map;
    }
}

获取自定义json异常数据总结:
1.自定义异常类

package com.wcy.springboot.exception;

public class MyUserException extends RuntimeException {

    public MyUserException() {
        super("用户名出错了");//自定义异常类
    }
}

2.自定义异常处理类

package com.wcy.springboot.controller;

import com.wcy.springboot.exception.MyUserException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

//专门用于处理异常的控制器
@ControllerAdvice
public class ExceptionHandler  {

/*    //当捕获到自己自定义的异常时触发
    @org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
    @ResponseBody
    public Map handlerException(Exception e){
        Map map=new HashMap<>();
        map.put("code","not username");
        map.put("msg","用户名没有找到");
        return map;
    }*/

    /**
     * 1.可以通过请求转发 转发到error请求
     * 2.这样就会被springboot的BasicErrorController处理 它内部就有两个自适应的方法
     * 3.获取请求域中的状态码 我们需要自定义
     *      Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
     * @param e
     * @return
     */
    @org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
    public String handlerException(Exception e, HttpServletRequest request){
        Map<String,Object> map=new HashMap<>();
        map.put("code","not username");
        map.put("msg","用户名没有找到");
        //传入我们的状态码 这样才能返回页面  默认返回200是成功 没法返回页面
        request.setAttribute("javax.servlet.error.status_code",500);
        request.setAttribute("etx",map);
        return "forward:/error";//转发到error请求
    }
}

3.自定义组件。

package com.wcy.springboot.component;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;
//是一个组件 让springboot识别这个类
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    //接受页面和json数据的属性
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //获取自定义异常处理类传过来的数据
        Map<String, Object> etx= (Map<String, Object>) webRequest.getAttribute("etx", 0);
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company","wcy");//自定义公司名称
        map.put("msg",etx);
        return map;
    }
}

你可能感兴趣的:(java,springboot)