2020-11-27 springboot错误页面解析

一、 springboot错误页面解析

参照ErrorMvcAutoConfiguration的错误处理自动配置。

1、错误页面处理的组件:
(1)、ErrorPageCustomizer:错误页面定制器
在访问出错时,出现4xx,5xx等错误,就会出发该定制器,并制定错误响应规则。在中有一个注册错误页面的方法,通过调用ServerProperties的get方法得到Errorproperties类。
在这里插入图片描述
在这里插入图片描述
最后在Errorproperties类得到处理错误信息的路径,即配置的路径,未配置则是/error。
在这里插入图片描述

(2)BasicErrorController:处理默认/error请求
BasicErrorController中有两个处理器,他们会根据HttpStatus status = getStatus(request)得到请求所携带的信息进行处理,然后返回HTML页面或json数据。如果accept是*/*则会按照json数据返回。
在这里插入图片描述
在这里插入图片描述
如果accept是text/htnl则会按照返回html页面。
在这里插入图片描述
在这里插入图片描述

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    @RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        
        去哪个页面作为错误页面;包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
	}

	@RequestMapping
	@ResponseBody    //产生json数据,其他客户端来到这个方法处理;
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

(3)DefaultErrorViewResolver:解析得到响应页面
BasicErrorController中调用了resolveErrorView方法,String.valueOf(status)得到状态码,通过resolve得到响应页面的路径。

@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(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);
		if (provider != null) {
            //模板引擎可用的情况下返回到errorViewName指定的视图地址
			return new ModelAndView(errorViewName, model);
		}
        //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面   error/404.html
		return resolveResource(errorViewName, model);
	}

(4)DefaultErrorAttributes:
页面共享信息,在getErrorAttributes中定义了一个map集合,向其中添加了时间戳,随后调用了
addStatus等方法,并判断状态码是否为null,并向其中添加相关状态信息,最后返回给页面。

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

在这里插入图片描述
2、定制错误页面
根据DefualtErrorViewresolver的的原理,可得得到两种定制方案:
(1)有模板引擎的情况下;创建error/状态码.html页面; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
可以命名为error/4**、error/5**等
页面能获取的信息包括:

			timestamp:时间戳

			status:状态码

			error:错误提示

			exception:异常对象

			message:异常消息

			errors:JSR303数据校验的错误都在这里

(2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找,命名规则相同;

但是以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

3、定制错误的json数据
(1)、捕获异常返回json数据

@ControllerAdvice
public class MyExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleException(Exception e){
        System.out.println(1);
        Map<String,Object> map = new HashMap<>();
        map.put("message",e.getMessage());
        map.put("ext", "错误");
        return map;
    }
}

需要注意的是:如果是NoHandlerFoundException,即404页面无法找到的异常时,需要另外设置参数,否则无法进入@ExceptionHandler之中。

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

(2)转发到/error进行自适应响应效果处理
通过请求转发,将捕获到的异常转发到BasicErrorController进行处理,这样就可以根据请求的方的类型自适应返回html或者json数据。

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(NoUserException.class)
    public String handleException(NoUserException e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("message1",e.getMessage());
        map.put("ext", "错误");
        return "forward:/error";
    }
}

注意:
        此处必须在request域中存入状态码后才可以进行转发,因为抛开异常来说,在转发到BasicErrorController这个过程是成功的,所以状态码会转成200,所以必须提前存入异常的状态码,才能在处理异常是通过getStatus获取到正确的状态码。进行正常的处理,但是这种方法存在的弊端就是:在DefaultErrorAttributes中已经定义好了能够返回的数据,我们自己定制的数据是无法一起返回的。

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);
            } catch (Exception var4) {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }
        }
    }

(3)返回定制数据
因为返回数据是由ErrorAttribute接口中的getErrorAttributes来获取的,所以我们只需要便编写一个ErrorAttribute接口的实现类,重写getErrorAttributes方法就可以了。为了简便,DefualtErrorAttribute作为ErrorAttribute实现类,我们只需要继承他,重写方法即可。
同时,也可以通过request域存入Exceptionhandler中定制的数据,在请求转发的时候一起传过来获取到。

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(NoUserException.class)
    public String handleException(NoUserException e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("message1",e.getMessage());
        map.put("ext", "错误");
        request.setAttribute("map",map);
        return "forward:/error";
    }
}

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes( webRequest,options);
        errorAttributes.put("毫秒值", new Date().getTime());
        Map map = (Map) webRequest.getAttribute("map", 1);
        errorAttributes.put("ex",map);
        return errorAttributes;
    }
}

注意:
        在重写的方法中,需要先调用父类的getErrorAttributes()才能保证原来规定的信息也能全部获取。webRequest其实是Request的封装对象,是RequestAttributes的子类,可以直接getAttribute,但是需要注明一个参数,0是从request域中取数据,1是从session域中获取数据

    int SCOPE_REQUEST = 0;
    int SCOPE_SESSION = 1;
    String REFERENCE_REQUEST = "request";
    String REFERENCE_SESSION = "session";

    @Nullable
    Object getAttribute(String var1, int var2);
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页