资讯详情

【springmvc】九大组件之HandlerExceptionResolver

在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()方法。

下面的这两种异常场景不适用:

  1. 请求没有到达DispatcherServlet的核心流程,如在filter中抛出异常。
  2. 请求进入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(
        标签: 对射光电开关传感器感应开关exe3akavlico位移传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台