Spring Web源码之核心组件(拦截器与异常处理)
Posted 木兮君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Web源码之核心组件(拦截器与异常处理)相关的知识,希望对你有一定的参考价值。
前言
上篇博文Spring Web源码之MVC主体结构主要讲了spring mvc整体结构以及主要核心流程,其实细心的小伙伴可以看出其实小编还遗留了一些没有讲到,比方说拦截器与异常处理器,今天小编带大家主要拦截器以及异常处理器。
在介绍拦截器与异常处理器之前,想问大家一个问题,拦截器和过滤器的区别,为什么有了拦截器还需要过滤器,小编看起来他们功能都是差不多的,其实过滤器针对servlet进行过滤,而拦截器是针对我们mvc中的handler进行拦截,上篇也有看到在执行handler的业务流程之前可以对其进行拦截。这是小编的一些自己的理解。好了进入正题。
核心组件
拦截器
HandlerInterceptor,大家看到Interceptor就差不多可以理解为拦击器了,大部分开源框架基本都有他是身影,那对于这样一个拦截器接口基本有三个方法:
- 前置拦截:调用方法之前的进行拦截。
- 后置拦截:调用方法之后进行拦截。
- 完成处理:在处理业务的时候发生错误了,那后置拦截就不会被处理,但是完成处理就会被处理,相当于finally中的逻辑
这些方法其实和动态代理有很大的相似,尤其是在做监控的时候,我们需要方法运行时间啊参数啊,报错后的截取啊等等。
接下来咱们来看看拦截的类封装以及拦截的流程:
容小编稍作解释:
- 首先是拦截器的方法如同小编上面所讲有三个接口,然后他被封装在执行器链中的List中
- 在获取hanler的时候其实是通过HandlerMapping找到的HandlerExecutionChain
- 之后获取适配器然后执行handler里面的方法时则会先调用HandlerExecutionChain中的前置后置方法,其实就是List中所有HandlerInterceptor的所有前置后置方法
- 然后从调用handler方法,创建视图以及渲染视图的时候,任意一个报错都会进入完成处理拦截中,不报错也会执行。
如果看过上篇博客中的源代码,再看这幅图的时候就比较简单了。
拦截器代码演示
拦截器有两种使用方式分别为:1、配置拦截器映射 2、直接注入到HandlerMapping里面去
配置拦截器映射(HandlerMapping映射相似):
spring-mvc.xml
<bean id="myInterceptor" class="com.lecture.mvc.MyInterceptor"/>
<!-- 第一种拦截配置-->
<bean class="org.springframework.web.servlet.handler.MappedInterceptor">
<constructor-arg index="0" type="java.lang.String[]">
<list>
<value>/requestHandler</value>
</list>
</constructor-arg>
<constructor-arg index="1" type="org.springframework.web.servlet.HandlerInterceptor"
ref="myInterceptor"/>
</bean>
<!--第二种拦截配置-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/requestHandler"/>
<ref bean="myInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
这里第二种配置其实是第一种配置的简化,其实质还是第一种配置。
这里小编一直有个不明白MappedInterceptor为什么会这样设计,他既是一个映射,里面包含了HandlerInterceptor又实现了HandlerInterceptor。看源码的时候其实他是去遍历的时候用到,感觉设计并不是很好。还有就是HandlerExecutionChain会有两个HandlerInterceptor的封装,设置的时候先设置到list中然后转到数组中去,spring mvc很多地方都会这么用。一个数组一个list有可能有多例或并发吧,不是很清楚
直接注入到HandlerMapping
spring-mvc.xml
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello">httpRequestHandlerTest</prop>
</props>
</property>
<property name="interceptors" ref="myInterceptor"/>
</bean>
第二种的配置比较少用,但是如果和第一种放在一起使用,那拦截器就会有一样的myInterceptor两次(看了源码之后就会明白)。其实第一种方法到最后也是放入到handlerMapping中去,当然中间进过一系列操作,那小编接着往下说
拦截器机制
拦截器是如何封装进去的,先看一下其流程图:
源码阅读
dispatchServlet中的getHandler最终会调用到org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception
Object handler = this.getHandlerInternal(request);
if (handler == null)
handler = this.getDefaultHandler();
if (handler == null)
return null;
else
if (handler instanceof String)
String handlerName = (String)handler;
handler = this.getApplicationContext().getBean(handlerName);
//这里将handler以及Interecptor合在一起
HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request))
CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
CorsConfiguration config = globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig;
executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
return executionChain;
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request)
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//url路径
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//遍历所有的HandlerInterceptor
for (HandlerInterceptor interceptor : this.adaptedInterceptors)
//如果是MappedInterceptor 拦截器映射则进行下一步判断
if (interceptor instanceof MappedInterceptor)
//是否和拦截器所配置的路径表达式想匹配,如果匹配则加入拦截器链中,这边拦截器强转了一次
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher))
chain.addInterceptor(mappedInterceptor.getInterceptor());
else
//直接添加到chain,这里的话就是第二种配置方法 直接注入的
//加入用两种方式配置则一样的interceptor有两个
chain.addInterceptor(interceptor);
return chain;
上面源码就是封装HandlerExecutionChain 的过程,当然还差所有拦截器的初始化过程,方法如下,
org.springframework.web.servlet.handler.AbstractHandlerMapping#initApplicationContext
代码比较简单小编就不继续贴出源码了,无法就是Interceptor以及在ioc容器里面然后根据不同的Interceptor,添加到里面去。
protected void initApplicationContext() throws BeansException
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
说完了拦截器,接下来进入异常处理
异常处理器
异常处理只能是在获取Handler,获取Handler适配器以及调用的过程中抛出异常,我们将跳转到哪个页面。异常处理可以配置异常处理的映射表,让不同的异常跳对应的视图界面,说到这儿我们先看一下异常处理的流程以及对应异常处理类:
这里稍微说明一点,当出现异常的时候,后置拦截是不会被执行的。
异常处理器代码演示
异常处理器有两种使用方式分别为:1、基于映射配置 2、基于注解
基于映射配置
就如上图一样,不同的异常对应不同的视图即可
spring-mvc.xml 下面是将IOException和IllegalArgumentException配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="error"/>
<property name="exceptionMappings" >
<props>
<prop key="java.io.IOException">IoError</prop>
<prop key="java.lang.IllegalArgumentException">IllegalError</prop>
</props>
</property>
</bean>
基于注解
跟映射配置不同的是上面是交给了HandlerExceptionResolver来帮忙封装创建的,而这个是咱们自己来封装的,具体看下面类:
@Controller
@ControllerAdvice
public class MethodHandler
@ExceptionHandler(value = IllegalArgumentException.class, IOException.class)
public ModelAndView handleException(Exception ex)
ModelAndView mv = new ModelAndView();
mv.addObject("exception", new RuntimeException("这个是由@ExceptionHandler捕捉的异常"));
mv.setViewName("error");
return mv;
@RequestMapping("/method/hello")
@ResponseBody
public String hello(HttpServletRequest request)
if (request.getParameter("illegalError") != null)
throw new IllegalArgumentException("参数错误");
if (request.getParameter("otherError") != null)
throw new RuntimeException("其它错误");
return "hello method";
这里@ControllerAdvice主要是捕获所有Controller的异常,如果不加只对本类有效,这里调用hello方法的时候就会被上面异常处理器捕获到,然后返回响应的页面。
源码阅读
首先在DispatchServlet初始化过程中会initHandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context)
this.handlerExceptionResolvers = null;
//一般这里都为true,意思为是否开启侦查所有的HandlerExceptionResolvers
if (this.detectAllHandlerExceptionResolvers)
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
//在Ioc容器中找到对应HandlerExceptionResolver类,不为空则加进去
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
//从IOC容器中拿name为handlerExceptionResolver的HandlerExceptionResolver
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)
//都为空则从配置中查找
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled())
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
初始化完之后又调用到doDispatch方法了,进入到创建异常视图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)
//异常为ModelAndViewDefiningException,则为modelAndView直接返回
//一般情况不会到这儿
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);
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared())
render(mv, request, response);
if (errorView)
WebUtils.clearErrorRequestAttributes(request);
else
if (logger.isTraceEnabled())
logger.trace("No view rendering, null ModelAndView returned.");
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted())
// Concurrent handling started during a forward
return;
if (mappedHandler != null)
mappedHandler.triggerAfterCompletion(request, response, null);
处理异常视图,所谓处理异常就是遍历handlerExceptionResolvers,如果可以处理就处理了,然后拿到异常的视图然后渲染。否则就报错了。
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)
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);
if (logger.isDebugEnabled())
logger.debug("Using resolved error view: " + exMv);
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
throw ex;
整体的代码还是比较容易懂的。
总结
今天小编主要是分享了spring mvc中的拦截器与异常处理器,还是那句话虽然很少配置了,但是期望小编能懂里面的原理,并且了解过里面的源码,做到心中有数,而并不是百度或原项目中copy完事。下次为大家带来视图解析(虽然好像已经不太用了),再接再厉加油!
以上是关于Spring Web源码之核心组件(拦截器与异常处理)的主要内容,如果未能解决你的问题,请参考以下文章
(34)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Zuul过滤器介绍及使用(传递数据拦截请求和异常处理)