【springmvc】九大组件之HandlerExceptionResolver
时间:2022-10-04 02:00:00
在Spring MVC如果不处理异常,Spring MVC将异常直接扔给容器。
例如,以下代码抛出异常:
@GetMapping("e1") public String exception() {
int i = 1 / 0; return "exception"; }
500个错误将显示在浏览器页面上。
处理异常
@ExceptionHandler处理当前Controller的异常
@ExceptionHandler能对当前Controller可通过处理中指定的异常ModelAndView将异常信息传输到页面。
使用如下:
@GetMapping("e1") public String exception() {
int i = 1 / 0; return "exception"; } @ExceptionHandler(value = RuntimeException.class) public ModelAndView handlerRuntimeException(Exception exception) {
ModelAndView mv = new ModelAndView("error"); mv.addObject("exception", exception); return mv; }
@ExceptionHandler要注意异常优先级,如果Controller有以下异常处理方法,仍将执行handlerArithmeticException()方法,因为ArithmeticException异常更具体:
@ExceptionHandler(value = RuntimeException.class) public ModelAndView handlerRuntimeException(Exception exception) {
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", exception);
return mv;
}
@ControllerAdvice处理全局异常
@ExceptionHandler的作用域是当前Controller,如果想对全局的异常进行处理需要使用@ControllerAdvice。
使用如下:
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
@ExceptionHandler(value = ArithmeticException.class)
public ModelAndView handlerArithmeticException(Exception exception) {
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", exception);
return mv;
}
}
如果Controller中使用了@ExceptionHandler处理异常,那么他的优先级比全局的异常处理高。
@ResponseStatus定制错误码和错误内容
可以使用@ResponseStatus加到异常类上,这样当这个异常抛出时,页面显示的是定制的错误消息。
异常类定义如下:
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "用户名和密码不匹配")
public class UsernameNotMatchPasswordException extends RuntimeException {
}
在方法中抛出这个异常:
/** * 使用@ResponseStatus定义错误码和错误内容 * @param i * @return */
@GetMapping("e3")
public String exception3(int i) {
if (13 == i) {
throw new UsernameNotMatchPasswordException();
}
return "success";
}
页面显示的错误消息如下:
使用SimpleMappingExceptionResolver处理指定异常
注入SimpleMappingExceptionResolver,并指定要处理的异常与视图。
com.morris.spring.mvc.config.MVCConfig#extendHandlerExceptionResolvers
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
// 指定要处理的异常与视图
properties.setProperty("java.lang.NullPointerException", "error");
simpleMappingExceptionResolver.setExceptionMappings(properties);
resolvers.add(simpleMappingExceptionResolver);
}
在Controller中抛出指定的异常:
/** * 使用SimpleMappingExceptionResolver处理异常 * * @return */
@GetMapping("e5")
public String exception5() {
if(true) {
throw new NullPointerException();
}
return "success";
}
这样就会跳到指定的视图error.jsp页面。
自定义HandlerExceptionResolver
如果觉得SpringMVC提供的异常处理不满足需求,可以实现HandlerExceptionResolver接口自定义异常处理。
package com.morris.spring.mvc.exception;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyHandlerExceptionResolver extends AbstractHandlerExceptionResolver implements PriorityOrdered {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if(ex instanceof ArrayIndexOutOfBoundsException) {
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", "MyHandlerExceptionResolver");
return mv;
}
return null;
}
/** * 优先级设置高一点,否则会被@ControllerAdvice全局异常处理 * @return */
@Override
public int getOrder() {
return 1;
}
}
总结:不管使用上面哪种异常处理方法,只能处理在请求到达了DispatcherServlet,并且出现了异常后进入processDispatchResult()方法。
下面的这两种异常场景不适用:
- 请求没有到达DispatcherServlet的核心流程,如在filter中抛出异常。
- 请求进入processDispatchResult()方法处理异常,但是在处理过程中有抛出了异常,如在@ControllerAdvice方法中抛出了异常。
源码分析
HandlerExceptionResolverComposite的调用过程
在处理controller的返回结果时,发现有异常就会调用异常处理的逻辑:
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 异常处理
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
... ...
org.springframework.web.servlet.DispatcherServlet#processHandlerException
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
/** * handlerExceptionResolvers什么时候初始化的? * @see DispatcherServlet#initHandlerExceptionResolvers(org.springframework.context.ApplicationContext) * * @see AbstractHandlerExceptionResolver#resolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) * @see HandlerExceptionResolverComposite#resolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) */
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
handlerExceptionResolvers何时被初始化的?
org.springframework.web.servlet.DispatcherServlet#initHandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
/** * HandlerExceptionResolver在WebMvcConfigurationSupport中注入了 */
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
// 默认ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
handlerExceptionResolvers是从在SpringMVC容器中获取所有的HandlerExceptionResolver实例,那么这些实例是什么时候注入的呢?
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
// 添加默认的HandlerExceptionResolver
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) {
// 添加ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
// 添加ResponseStatusExceptionResolver
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
// 添加DefaultHandlerExceptionResolver
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
默认注册了三个HandlerExceptionResolver:
- ExceptionHandlerExceptionResolver
- ResponseStatusExceptionResolver
- DefaultHandlerExceptionResolver
我们可以通过实现WebMvcConfigurer接口extendHandlerExceptionResolvers()方法注入自己的HandlerExceptionResolver,也可以通过向SpringMVC容器中注入HandlerExceptionResolver实例。
最后ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver以及通过实现WebMvcConfigurer接口的extendHandlerExceptionResolvers()方法注入的HandlerExceptionResolver都会包装到HandlerExceptionResolverComposite中。
最后异常处理都会调用HandlerExceptionResolverComposite#resolveException
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
/** * @see AbstractHandlerExceptionResolver#resolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) */
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
然后会调用各个HandlerExceptionResolver的doResolveException()进行异常处理。
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
/** * @see DefaultHandlerExceptionResolver#doResolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) * @see ResponseStatusExceptionResolver#doResolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) * @see SimpleMappingExceptionResolver#doResolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) * @see AbstractHandlerMethodExceptionResolver#doResolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception) * */
// 让所有的ExceptionResolver按顺序挨个处理,有视图返回就救赎
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver从名字就可以看出是处理@ExceptionHandler注解的。
首先看下ExceptionHandlerExceptionResolver的afterPropertiesSet方法,主要解析@ControllerAdvice注解的类中的带有@ExceptionHandler的方法,建议异常与方法之间的映射关系,注意这里只解析了@ControllerAdvice注解的类中的带有@ExceptionHandler注解的方法,并没有解析Controller中带有ExceptionHandler注解的方法。
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet
public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans // 解析带有@ControllerAdvice注解的类 initExceptionHandlerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite(元器件数据手册
、IC替代型号,打造电子元器件IC百科大全!