SpringBoot 的错误处理机制

1,大致流程

一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

2,错误自动配置类

ErrorMvcAutoConfiguration ,此类帮我们自动配置了:

	@Bean
	public ErrorPageCustomizer errorPageCustomizer() {
		return new ErrorPageCustomizer(this.serverProperties);
	}

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
       /*添加错误映射,即发生错误时会发送 /error 请求*/
	   @Override
		public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {            
			ErrorPage errorPage = new ErrorPage(
					this.properties.getServlet().getServletPrefix()
							+ this.properties.getError().getPath());         
			errorPageRegistry.addErrorPages(errorPage);
		}
}

public class ErrorProperties {
	@Value("${error.path:/error}")
	private String path = "/error";

3,BasicErrorController 处理 /error 请求

/*返回页面响应*/
@RequestMapping(produces = "text/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);
        /*如果没有找到对应的 /error/5xx.html 视图 就会返回一个名为 error 的ModelAndView*/
		return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
	}
/*返回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);
        /*将响应状态码和响应数据封装成ResponseEntity 返回*/
		return new ResponseEntity<>(body, status);
	}

①,getStatus

protected HttpStatus getStatus(HttpServletRequest request) {
        /*从request 获取key为 javax.servlet.error.status_code 的值*/
		Integer statusCode = (Integer) request
				.getAttribute("javax.servlet.error.status_code");
		if (statusCode == null) {
            /*获取不到就响应一个500*/
			return HttpStatus.INTERNAL_SERVER_ERROR;
		}
		try {
			return HttpStatus.valueOf(statusCode);
		}
		catch (Exception ex) {
            /*发生错误也响应一个500*/
			return HttpStatus.INTERNAL_SERVER_ERROR;
		}
	}

②,getErrorAttributes 获取错误响应数据

public abstract class AbstractErrorController implements ErrorController {
	private final ErrorAttributes errorAttributes;
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
			boolean includeStackTrace) {
		WebRequest webRequest = new ServletWebRequest(request);
        /*就是调用ErrorAttributes  里的 getErrorAttributes 方法获取响应数据*/
		return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
   }
}

public class ErrorMvcAutoConfiguration {
/*为我们注册了一个DefaultErrorAttributes */
@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes(
				this.serverProperties.getError().isIncludeException());
	}
}
//即调用了DefaultErrorAttributes 的 getErrorAttributes 方法,如下:
	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}
/*
总的来说就是放了如下数据
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
*/

③,name为error 的ModelAndView

	public class ErrorMvcAutoConfiguration {
    @Configuration
	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
	@Conditional(ErrorTemplateMissingCondition.class)
	protected static class WhitelabelErrorViewConfiguration {
        //这个即我们经常看到的错误页
		private final SpelView defaultErrorView = new SpelView(
				"<html><body><h1>Whitelabel Error Page</h1>"
						+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
						+ "<div id='created'>${timestamp}</div>"
						+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
						+ "<div>${message}</div></body></html>");
       // 即是这个View 
		@Bean(name = "error")
		@ConditionalOnMissingBean(name = "error")
		public View defaultErrorView() {
			return this.defaultErrorView;
		}

}

4,定制错误响应

①,编写一个错误处理类,这里还是用到了@ControllerAdvice 注解

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

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
//声明是一个错误处理器
@ControllerAdvice
public class ErrorControllAdvice {

//    处理那种类型的错误
    @ExceptionHandler(Exception.class)
    public String errorHandle(HttpServletRequest request){
//        添加额外错误响应数据
        Map<String,Object> map=new HashMap<>();
//        添加一个公司的响应数据
        map.put("company","小米");
//        放到request 中,然后在我们定制的DefaultErrorAttributes 里获取
        request.setAttribute("ext",map);
//        设置响应状态码
        request.setAttribute("javax.servlet.error.status_code",500);
//        转发到/error 请求,这样就能根据客户端优化接收何种类型数据,决定响应html还是json
        return "forward:/error";
    }
}

②,定制DefaultErrorAttributes

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

import java.util.Map;

@Component
public class MyDefaultErrorAttributes extends DefaultErrorAttributes {
    /*重写getErrorAttributes 方法*/
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//        获取原来的响应数据
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //从请求域中拿值
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
        //添加我们定制的响应数据
        map.put("ext",ext);
//        返回带有我们定制的数据的map
        return map;
    }
}

猜你喜欢

转载自my.oschina.net/u/3574106/blog/1823125