路还在继续,梦还在期许
1、错误处理
1.1、默认规则
- 默认情况下,spring boot 提供 /error 处理所有错误的映射。
- 对于机器客户端(app),它将生成 JSON 响应,其中包含错误,HTTP状态和异常消息的详细信息
- 对于浏览器客户端,响应一个 “ whitelabel” 错误视图,以HTML格式呈现相同的数据。
1.2、自定义错误页面
- 要对其进行自定义,添加 View 解析为 error。
- 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加 ErrorAttributes 类型的组件以使用现有机制但替换其内容。
- error/下的4xx,5xx页面会被自动解析。
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
1.3、定制错误处理逻辑
1.3.1、自定义错误页
error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页。
1.3.2、@ControllerAdvice+@ExceptionHandler处理全局异常
底层是 ExceptionHandlerExceptionResolver 支持的(推按使用这种方式)
// 处理整个web controller的异常
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({
ArithmeticException.class, NullPointerException.class}) // 异常处理
public String handlerArithException(Exception e){
log.error("异常是:{}",e);
return "login"; // 视图地址
}
}
1.3.3、@ResponseStatus+自定义异常
底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息组装成ModelAndView返回。
底层调用 response.sendError(statusCode, resolvedReason);这样就结束了,给tomcat发送的错误 /error。
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "用户数量太多") // 给页面返回一个异常状态码403,和错误的原因
public class UserTooManyException extends RuntimeException {
public UserTooManyException() {
}
public UserTooManyException(String message) {
super(message);
}
}
1.3.4、Spring底层的异常
如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
最后调用 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
此次请求结束,给Tomcat服务器发送错误,/error 谁能处理就处理,无人处理就响应Tomcat原生错误页。
1.3.5、自定义实现 HandlerExceptionResolver 处理异常
可以作为默认的全局异常处理规则
@Order(value = Ordered.HIGHEST_PRECEDENCE) // 指定优先级
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
response.sendError(511, "自定义错误");
} catch (IOException e) {
throw new RuntimeException(e);
}
ModelAndView modelAndView = new ModelAndView();
return modelAndView;
}
}
1.3.6、ErrorViewResolver 实现自定义处理异常
response.sendError ,error请求就会转给controller。
转给controller的两种方式:
方式一:
调用 response.sendError 转给 controller。
方式二:
你的异常没有任何人能处理,tomcat底层 response.sendError,error请求就会转给controller。
解析规则:
basicErrorController 要去的页面地址是 ErrorViewResolver ;
1.4、异常处理自动配置原理
位置:org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
ErrorMvcAutoConfiguration 自动配置异常处理规则
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({
Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({
ServerProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
}
1.4.1、DefaultErrorAttributes 类
容器中添加组件:类型:DefaultErrorAttributes -> id:errorAttributes
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
DefaultErrorAttributes类,实现了 ErrorAttributes 接口和 HandlerExceptionResolver 接口。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
}
DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
storeErrorAttributes(request, ex);
return null;
}
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (Boolean.TRUE.equals(this.includeException)) {
options = options.including(Include.EXCEPTION);
}
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.put("message", "");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
1.4.2、BasicErrorController 类
容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
}
它的两个处理方法:
响应JSON
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
}
响应HTML
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
1.4.3、内部类 ErrorPageCustomizer 类
容器中有组件 View->id是error;(响应默认错误页)
容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final StaticView defaultErrorView = new StaticView();
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)
1.4.4、错误视图解析器
位置:org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver 接口
@FunctionalInterface
public interface ErrorViewResolver {
/**
* Resolve an error view for the specified details.
* @param request the source request
* @param status the http status of the error
* @param model the suggested model to be used with the view
* @return a resolved {@link ModelAndView} or {@code null}
*/
ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}
位置:org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver
实现 ErrorViewResolver 接口
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
}
在过时的配置中配置了 DefaultErrorViewResolver 组件
容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({
org.springframework.boot.autoconfigure.web.ResourceProperties.class,
WebProperties.class, WebMvcProperties.class })
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final Resources resources;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
WebProperties webProperties) {
this.applicationContext = applicationContext;
this.resources = webProperties.getResources().hasBeenCustomized() ? webProperties.getResources()
: resourceProperties;
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
}
如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面。
在 error 路径下找 404.html、5xx.html。
1.5、异常处理步骤流程
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException 封装
2、进入视图解析流程(页面渲染)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
- 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
- 2、系统默认的异常解析器;
○ 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
○ 2、默认没有任何人能处理异常,所以异常会被抛出
■ 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
■ 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
■ 3、默认的 ErrorViewResolver 作用是把响应状态码作为错误页面地址返回 error/状态码.html
■ 4、模板引擎最终响应这个页面 error/500.html