SpringMvc错误处理

默认的处理

浏览器访问不存在的页面

在这里插入图片描述

用postman访问不存在的页面

在这里插入图片描述

原理源码分析

springboot所有的配置都是使用自动配置的,如***AutoConfiguration来注册组件,后面需要的时候再使用

调用ErrorMvcAutoConfiguration来注册组件

  • DefaultErrorAttributes
  • BasicErrorController
  • WhitelabelErrorViewConfiguration
    这个用来给出最上面的没有任何模板情况下的一种处理,这里初始化了SpelView做为默认的错误视图

主要步骤

1. DispatchServlet的doDispatch方法

// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

这里的ha就是RequestMappingHandlerAdapterHandlerMethod就是BasicErrorController

2. handlerInternal返回ModelAndView

			mav = invokeHandlerMethod(request, response, handlerMethod);

通过反射,调用 调到BasicErrorController的error方法

3. 这里针对Html与json有不同的处理

  • 3.1 针对 html的处理

@RequestMapping(produces = "text/html")
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
			//获得响应的状态码信息如404
		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);
	}
  • 获得错误信息,加入到map中
    这里调用 DefaultErrorAttributes#getErrorAttributes方法来获得,DefaultErrorAttributes是springboot 开始自动注入的
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;
	}
	```
- 解析错误视图,实际上调用 的是
DefaultErrorViewResolver#resolveErrorViewresolveErrorView
```java
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map<String, Object> model) {
			//如果能精确找到有状态码的,优先返回
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
		//如果没有找到精确的,那么就找系列的,即4xx,5xx
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

调用DefaultErrorViewResolver#resolve来解析渲染视图

private ModelAndView resolve(String viewName, Map<String, Object> model) {
		String errorViewName = "error/" + viewName;
		
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		//检查是否有模板引擎,如果有用模板引擎,使用模板引擎处理
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		//没有模板引擎的处理,在静态文件目录下查找并处理
		return resolveResource(errorViewName, model);
	}
  • 问题:没有模板引擎的处理,按理说应该会返回最开始的空白页面,可是并没有看到处理呢?
    其实上面确实没有处理,上面的所有步骤都是在DispatcherServelet的主流程:
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    这里的mv其实就是错误的视图
    最后会调用
    processDispatchResult来处理,跟进代码
    //1.调用render来渲染
    render(mv, request, response)
    //具体是调用SpelView.render来渲染
    @Override
    		public void render(Map<String, ?> model, HttpServletRequest request,
    				HttpServletResponse response) throws Exception {
    			if (response.getContentType() == null) {
    				response.setContentType(getContentType());
    			}
    			Map<String, Object> map = new HashMap<String, Object>(model);
    			map.put("path", request.getContextPath());
    			PlaceholderResolver resolver = new ExpressionResolver(getExpressions(), map);
    			String result = this.helper.replacePlaceholders(this.template, resolver);
    			response.getWriter().append(result);
    		}
  • 3.2 非html处理,过程基本同上,不过返回的是json格式,即没有模板引擎进行渲染

@RequestMapping
	@ResponseBody
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		//获取错误信息
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		//检查状态
		HttpStatus status = getStatus(request);
		//封装成json对象
		return new ResponseEntity<Map<String, Object>>(body, status);
	}

如何定制错误

1. 定制页面

1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里

DefaultErrorAttributes#getErrorAttributes
private void addErrorDetails(Map<String, Object> errorAttributes,
			RequestAttributes requestAttributes, boolean includeStackTrace) {
		//JSR303处理bindingresult
		Throwable error = getError(requestAttributes);
		if (error != null) {
			while (error instanceof ServletException && error.getCause() != null) {
				error = ((ServletException) error).getCause();
			}
			errorAttributes.put("exception", error.getClass().getName());
			addErrorMessage(errorAttributes, error);
			if (includeStackTrace) {
				addStackTrace(errorAttributes, error);
			}
		}
		Object message = getAttribute(requestAttributes, "javax.servlet.error.message");
		if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
				&& !(error instanceof BindingResult)) {
			errorAttributes.put("message",
					StringUtils.isEmpty(message) ? "No message available" : message);
		}
	}
		2)、没有模板引擎(即templates目录下面没有这个错误页面的文件),静态资源文件夹下找;
如果静态文件下能找到,也显示,不过变量不会被解析
		3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

2. 定制json

2.1 自定义异常处理&返回定制json数据;

@ControllerAdvice
public class MyExceptionHandler {

@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
    Map<String,Object> map = new HashMap<>();
    map.put("code","user.notexist");
    map.put("message",e.getMessage());
    return map;
}

}
//没有自适应效果,即这个只能返回Json,并不能返回html页面。

2.2 转发到/error进行自适应响应效果处理

forward与redirect的区别??这里为什么redirect不行?to do

forward相当于重新发了一次请求,redirect只是重定向到某一个页面。

 @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx,否则就不会进入定制错误页面的解析流程
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        //转发到/error
        return "forward:/error";

如果不传入500的状态码,页面又会返回默认的错误页面,这是为什么呢?

private ModelAndView resolve(String viewName, Map<String, Object> model) {
//如果这里的viewName是200,那么就会找模板引擎下的200.html,然而我们并没有这个页面,因此找不到,于是就找到了默认的处理页面
//当然我们这里可以定义其它状态码的,都 没问题。
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

添加自己自定的数据

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);

1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

		容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;

自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        map.put("company","atguigu");
        return map;
    }
}

最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/mingtiandexia/article/details/88811867
今日推荐