springboot09-错误处理机制

一、错误处理机制

1)、SpringBooot默认的错误处理机制

默认效果:

​ 1)、返回一个默认的错误页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQcTZsYS-1583755847874)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309131511952.png)]

​ 2)、如果是其他客户端,默认响应一个json数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCX8c3Rb-1583755847882)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309131700264.png)]

	浏览器发送请求的请求头:text/html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2McUAy2F-1583755847884)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309133128137.png)]

​ 其他客户端发送请求的请求头: /*

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Bh780wE-1583755847889)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309133046034.png)]

原理:

​ 可以参照ErrorMvcAutoConfiguration;自动配置原理;

​ 给容器中添加了以下组件:

​ 1、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;
    }


​ 2、BasicErrorController:处理error请求

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }
//这个产生html类型的数据,浏览器发送的请求通过这个方法进行处理
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
   		//ModelAndView是要去哪个页面作为错误页面--->ModeAndView是页面地址+页面内容
    	//这里去拿resolve的解析器对modelView进行解析
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
//这个产生的是json数据,其他客户端发送的请求通过这个方法进行处理
@RequestMapping
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.isIncludeStackTrace(request, MediaType.ALL));
        return new ResponseEntity(body, status);
    }
}

​ 3、ErrorPageCustomizer

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})

​ 4、DefaultErrorViewResolver

public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        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) {
        //默认springboot可以去找到某个页面--->error/404 
        String errorViewName = "error/" + viewName;
        
        //模版引擎可以解析这个页面地址就用模版引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        
        //如果模版引擎可以用的情况下就返回到errorViewName指定的视图地址。
        //反之就在静态资源文件夹下找到errorViewName对应的页面 error/404.html
        return provider != null ? new ModelAndView(errorViewName, model) : 			this.resolveResource(errorViewName, model);
    }

步骤:

​ 一但系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,随后被BasicErrorController处理。

​ 1)、响应页面; 去哪个页面是由DefaultErrorViewResolver解析出来的,这个DefaultErrorViewResolver又是我们添加到容器中的组件,所以只需要去找到这个组件即可;

2)、如何定制错误响应:

​ 1)、如何定制错误响应的页面:

​ 1.有模版引擎的情况下:error/状态码【将错误页面命名为 错误状态码.html】,发生此状态码的错误就会来到对应的错误页面。比如error/404.html;我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)。

​ 页面能获取的信息:

​ (1)timestamp:时间戳

​ (2)status:状态码

​ (3)error:错误提示

​ (4)exception:异常对象

​ (5)message:异常消息

​ (6)JSR303数据校验信息

​ 2.没有模版引擎的情况下:自定义的404xx页面也可以放在静态static文件夹下,这样一样可以显示自定义错误页面,但是static下的页面无法加载动态信息。

​ 3.以上都没有错误页面,就是默认来到SpringBoot默认的错误error页面。

2)、如何定制错误的json数据:

​ 1、创建一个MyExceptionHandler.class

@ControllerAdvice
public class MyExceptionHandler {
    //自定义处理异常
    @ResponseBody
    @ExceptionHandler(UserPrincipalNotFoundException.class)
    public Map<String, Object> handleException(Exception e){
        Map<String,Object> map=new HashMap<>();
        map.put("code:","user.PrincipalNotFound");
        map.put("message:",e.toString());
        return map;
    }
}

在这个自定义异常的类中,我们可以指定不同类别的Exception错误所返回的json数据列表,我们这里用的是Map,SpringBoot会自动把Map转为Json。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNd0XRkz-1583755847894)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309171725549.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yaVTtVW-1583755847896)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309171828049.png)]

但是这个没有自适应效果:浏览器返回页面,客户端返回json。

我们需要把这个改为自适应情况。

​ 2、自适应浏览器和客户端

	@ExceptionHandler(UserPrincipalNotFoundException.class)
    public String handleException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code:", "user.PrincipalNotFound");
        map.put("message:", e.toString());
        return "forward:/error";
    }

我们只需要直接转发到forward:/error适配器就可以了。但是一定要传入状态码。

Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");

这个是BasicErrorController源码中获取状态码的代码。所以我们就要在map中设置javax.servlet.error.status_code这个值。

@ExceptionHandler(UserPrincipalNotFoundException.class)
    public String handleException(Exception e, HttpServletRequest request) {
//(Integer)request.getAttribute("javax.servlet.error.status_code");
        //设置自己的状态码
        request.setAttribute("javax.servlet.error.status_code",500);
        Map<String, Object> map = new HashMap<>();
        map.put("code:", "user.PrincipalNotFound");
        map.put("h1:","aaaa");
        map.put("message:", e.toString());
        //我们需要把map放到request中,才可以带走
        
        return "forward:/error";
    }

但是 我们发现,我们自己自定义的map错误提示没有传到前端页面去,也没有传入到json中,仔细观察可以看出,我们没有把map传回自定义的错误机制中。

我们现在就要把自定义的数据传出去:

我们来看一下我们自定义的过程:

出错—>自定义错误变量–>/error请求–>BasicErrorController处理(这个地方做了自适应效果{页面、客户端})

响应出去可以获取的数据是由getErrorAttributis得到的(是AbstractErrorController(就是ErrorController)规定的方法);

1、方法一:

​ 那么我们就可以完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

2、方法二:

​ 页面上【json返回】的数据都是通过this.errorAttributes.getErrorAttributes得到的;这就是一个

 private final ErrorAttributes errorAttributes;

然后我们来看这个的自动配置情况:

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

我们 可以看到,如果在容器中没ErrorAttributes.class这个组件,就new一个新的默认ErrorAttrIbutes对象–>DefaultErrorAttributes;并且调用的是DefaultErrorAttribute.getErrorAttributes函数:

我们可以再来看看这个函数的实现:

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;
    }

我们就可以自己来实现一个getErrorAttributes这个函数,就可以传入数据了。

自定义ErrorAttributies:

//给容器中加入我们自己定义的错误属性
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //调用父方法
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company","ogj");
        return map;
    }

这样我们就可以访问到我们设置的属性了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERIG6H1B-1583755847899)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309194222415.png)]

但是我们还想把Exception的信息也传过来,那么我们就可以使用resquest来存取:

@ExceptionHandler(UserPrincipalNotFoundException.class)
    public String handleException(Exception e, HttpServletRequest request) {
//(Integer)request.getAttribute("javax.servlet.error.status_code");
        //设置自己的状态码
        request.setAttribute("javax.servlet.error.status_code",500);
        Map<String, Object> map = new HashMap<>();
        map.put("code:", "user.PrincipalNotFound");
        map.put("h1:","aaaa");
        map.put("message:", e.toString());
        //我们需要把map放到request中,才可以带走

        request.setAttribute("ext",map);
        return "forward:/error";
    }
//返回值的map就是页面和json能获取的字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //调用父方法
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company","ogj");
        //WebRequest就是一个RequestAttribute
        //0是request  1是session
        Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }

这样我们就可以获取到我们设置的自定义错误了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6yJJHLF-1583755847900)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309194954464.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yXadtL9D-1583755847903)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200309195609801.png)]

最终效果:错误处理是可以自适应的,可以通过定制ErrorAttributes改变需要返回的内容。

发布了65 篇原创文章 · 获赞 29 · 访问量 6479

猜你喜欢

转载自blog.csdn.net/qq_41617848/article/details/104760058
今日推荐