SpringMVC 异常处理机制,
Posted QQ_851228082
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVC 异常处理机制,相关的知识,希望对你有一定的参考价值。
首先异常处理属于SpringMVC模块,而不属于spring core,有这种意识能帮助我们更好的理解spring framework。SpringMVC统一异常处理机制,一共有两种,第一种实现HandlerExceptionResolver
接口、第二种在异常处理类上添加@ControllerAdvice
注解,在方法上加@ExceptionHandler
处理指定异常类型。
实现HandlerExceptionResolver
HandlerExceptionResolver
只有一个resolveException
方法,返回参数ModelAndView
,前后端不分离时通常使用这种方式,大体逻辑是判断是否是ajax请求,如果是ajax请求,则返回null,否则返回非null的ModelAndView。
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex);
}
DefaultHandlerExceptionResolver
是此接口的默认实现,看一下DefaultHandlerExceptionResolver#doResolveException实现。
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
//这就是常见的,状态码405,请求方法不正确导致
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
//这就是常见的,状态码415,媒体类型不正确导致
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
//其他逻辑
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
//本类处理不了,返回null,以让后边的ExceptionResolver处理
return null;
}
这是第一种处理异常的方法。
@ControllerAdvice、@ExceptionHandler
写一个类,添加@ControllerAdvice
(更通常的使用@RestControllerAdvice
),在类的方法上添加@ExceptionHandler,并指明要处理的Exception类型。
@ControllerAdvice
public class MyExceptionHanlder{
//注解可以不指定异常,方法参数上指定
@ExceptionHandler
public ModelAndView handleException1(Exception1 excep){
//只处理Exception1类型的异常
}
//也可以配置在注解上,优先级大于方法参数,一般用来缩小可处理的异常范围
@ExceptionHandler({Exception2.class})
public ModelAndView handleException1(Exception2Parent excep){
//只处理Exception1类型的异常
}
@ExceptionHandler
public ModelAndView handleException(Exception excep){
//兜底,处理其他未处理的异常
}-.
}
@RestControllerAdvice适合前后端分离情况,它等于@ControllerAdvice
+@ResponseBody
@RestControllerAdvice
public class MyExceptionHanlder{
@ExceptionHandler
public MyVO handleException1(Exception1 excep){
}
}
这是第二种处理异常的方法。
SpringMVC中返回值统一处理
有时需要对返回值做统一处理,比如对返回值统一包装为{code:200,msg:"",data:object}
,controller方法返回参数可以是POJO,而不必是Result<T>
的样子,那么就需要实现ResponseBodyAdvice
接口,它在@ResponseBody或者ResponseEntity之后,但在HttpMessageConverter之前执行,通常与异常处理类一起使用。
@RestControllerAdvice
public class MyExceptionHanlder implements ResponseBodyAdvice<Object>{
@ExceptionHandler
public MyVO handleException1(Exception1 excep){
}
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType){
return true;
}
//在这里将返回值包装为统一数据类型
Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response){
if(body.getClass != Result.class){
return Result<>(body);
}
return body;
}
}
springmvc 异常处理机制原理
MvcNameSpaceHandler
解析<mvc:annotation-config/>
时依次注入ExceptionHandlerExceptionResolver
、ResponseStatusExceptionHandler
(用来处理ResponseStatusException
)、DefaultHandlerExceptionResolver
public BeanDefinition parse(Element element, ParserContext context) {
// 先注册ExceptionHandlerExceptionResolver,用来处理加了@ExceptionHandler方法抛出的异常
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.getPropertyValues().add("order", 0);//优先级最高
String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);
// 再注册ResponseStatusExceptionResolver
RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
statusExceptionResolver.getPropertyValues().add("order", 1);//优先级其次
String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
//然后注册DefaultHandlerExceptionResolver
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.getPropertyValues().add("order", 2);//优先级再次
String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);
}
然后 DispatcherServlet#initStrategies
初始化异常解析器类。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
//初始化处理异常解析器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这一步中除了系统内部定义的ExceptionResolver,还会将自定义HandlerExceptionResolver加入到处理链中。
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
//初始化ExceptionResolver
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);
}
}
}
遇到异常时会依次执行上边注册的ExceptionHandlerExceptionResolver
、ResponseStatusExceptionHandler
、DefaultHandlerExceptionResolver
,
可以看到这三个类都是HandlerExceptionResovler的子类。
springBoot的初始化ExceptionResolver过程
在
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver 中,将ExceptionHandlerExceptionResolver
、ResponseStatusExceptionResolver
、DefaultHandlerExceptionResolver
封装成HandlerExceptionResolverComposite
@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;
}
在org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHandlerExceptionResolvers中,将3种ExceptionResolver封装成HandlerExceptionResolverComposite
的实现原理。
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) {
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);
}
//解析含有@ControllerAdvice的类
exceptionHandlerResolver.afterPropertiesSet();
//先添加ExceptionHandlerExceptionResolver
exceptionResolvers.add(exceptionHandlerResolver);
//再添加ResponseStatusExceptionResolver
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
//最后添加DefaultHandlerExceptionResolver
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
@ExceptionHandler的实现原理
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache中搜索@ControllerAdvice的bean
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
然后在org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver中查找本类及父类含有@ExceptionHandler的方法
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
HandlerExceptionResolverComposite中ExceptionResolver的执行顺序
顺序执行,直到返回非null的ModelAndView,这一点与org.springframework.web.servlet.DispatcherServlet#processHandlerException是一样的。
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
初次之外,dispatchservlet还注册了DefaultErrorAttributes
、
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
总结
从上可以看出,springmvc与springboot 的异常处理内部实现原理有点差异,springboot兼容了springmvc。
- springmvc
- DispatcherServlet中注册了
ExceptionHandlerExceptionResolver
、ResponseStatusExceptionHandler
(用来处理ResponseStatusException)、DefaultHandlerExceptionResolver
及自定义HandlerExceptionResolver
- DispatcherServlet中注册了
- springboot
- DispatcherServlet中注册了,
DefaultErrorAttributes
、HandlerExceptionResolverComposite
及自定义HandlerExceptionResolver,而HandlerExceptionResolverComposite
内又依次执行ExceptionHandlerExceptionResolver
、ResponseStatusExceptionResolver
、DefaultHandlerExceptionResolver
,只要ExceptionHandlerExceptionResolver匹配了异常则不再执行后续HandlerExceptionResolver。
- DispatcherServlet中注册了,
以上是关于SpringMVC 异常处理机制,的主要内容,如果未能解决你的问题,请参考以下文章