SpringMVC学习记录5
Springmvc流程中的扩展点有很多,可以在很多地方插入自己的代码逻辑达到控制流程的目的.
如果要对Controller的handler方法做统一的处理.我想应该会有很多选择,比如:@ModelAttribute @InitBinder @ExceptionHandler @ControllerAdvice等注解,自己写AOP包裹Controller,Interceptor等等..
我从来没有用过Interceptor,所以最近稍微花了点时间研究了下.
流程
先看看在Springmvc中Interceptor拦截器是在什么时候被调用的.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
从dispatcherServlet中可以看出大致的工作流程:
第44行是我们比较熟悉的,让HandlerAdapter来调用我们自己写的Controller的handler来处理request.
在此之前的39行是调用包装了handler的HandlerExecutionChain的前置处理方法,第51行是调用了HandlerExecutionChain的后置处理方法.
而HandlerExecutionChain的applyPreHandle与applyPostHandle里面会调用拦截器的相关方法.
HandlerExecutionChain
handler方法会和各种Interceptor包装成HandlerExecutionChain给dispatcherServlet调用,所以有必要看看HandlerExecutionChain里的方法.
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
} /**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
} /**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception { HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
这3个方法刚好对应了HandlerInterceptor里的3个方法:
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception; void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception; void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
从Interceptor方法名字中我们就可以看出来这3个方法的作用:handler方法之前调用,handler方法之后调用,dispatch之后调用(视图渲染完之后).
如果配置了多个拦截器,从HandlerExecutionChain中的3个方法里也可以看出:
1.如果前置处理的顺序是拦截器1,拦截器2,拦截器3.那后置处理的顺序是拦截器3,拦截器2,拦截器1
2.如果一个拦截器前置处理的时候返回了false.那么后面的拦截器就不需要执行了.比如拦截2前置方法返回false.那拦截器3就不会执行.
3.在2前置处理失败的情况下,..调用过前置处理方法的拦截器的afterCompletion方法会被调用,用来清理资源...从代码中可以看出afterCompletion和后置处理方法是一样,也是拦截器顺序的倒序执行的,不过只有前置方法返回true的拦截器才会触发(因为拦截器3都没触发,所以不需要触发afterCompletion来清理资源).
多个拦截器的顺序
从HandlerExecutionChain中已经可以看出配置多个拦截器时候的调用顺序了...那怎么确定配置的多个拦截器在HandlerExecutionChain中的加载顺序呢?
实践结果
我实践的结果是在XML里配置的顺序就是加载顺序..
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.labofjet.controller.TestInteceptor1"></bean>
</mvc:interceptor> <mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.labofjet.controller.TestInteceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
比如这样就是先进入拦截1,再进入拦截2.
分析思考过程
记录一下我的思考过程,便于以后复习:
从DispatcherServlet的doDispatcher放中会调用这个方法:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
说明HandlerExecutionChain 来自HandlerMapping,在一般的情况下应该是RequestMappingHandlerMapping.因为RequestMappingHandlerMapping的order是最高的.所以它返回了handler以后就直接作为方法的结果返回了.
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}
L15说明HandlerExecutionChain来自getHandlerExecutionChain方法.
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}
interceptor来么来自adaptedInterceptors要么来自mappedInterceptors.
打断点发现是来自mappedInterceptors,而adaptedInterceptors是给我们自己扩展去加载自定义拦截器用的.
再思考下:
RequestMappingHandlerMapping是初始化Springmvc的时候加载的bean,Controller的handler方法是Springmvc初始化的时候就找出来并包装好的.所以Interceptor没有理由不在Springmvc初始化的时候找出来并包装好.毕竟这handler和interceptor功能都差不多....
我粗略看了下RequestMappingHandlerMapping在初始化的时候似乎只找到了2个地方可以参与Spring声明周期:
第一个是:
/**
* Detects handler methods at initialization.
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
} /**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #isHandler(Class)
* @see #getMappingForMethod(Method, Class)
* @see #handlerMethodsInitialized(Map)
*/
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
} String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
afterPropertiesSet这里是为了找到所有handler方法.
第二个是:
/**
* Initializes the interceptors.
* @see #extendInterceptors(java.util.List)
* @see #initInterceptors()
*/
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
这里initApplicationContext就是初始化interceptor用的.
前面实践打断点发现是mappedInterceptors,所以是调用detectMappedInterceptors来加载我定义的拦截器的.
/**
* Detect beans of type {@link MappedInterceptor} and add them to the list of mapped interceptors.
* <p>This is called in addition to any {@link MappedInterceptor}s that may have been provided
* via {@link #setInterceptors}, by default adding all beans of type {@link MappedInterceptor}
* from the current context and its ancestors. Subclasses can override and refine this policy.
* @param mappedInterceptors an empty list to add {@link MappedInterceptor} instances to
*/
protected void detectMappedInterceptors(List<MappedInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(), MappedInterceptor.class, true, false).values());
}
这个方法其实就是把所有MappedInterceptor类型的类找出来而已...我们定义的类型不是MappedInterceptor的子类..想必有什么地方把我们定义的Interceptor放到了MappedInterceptor类中.那是哪里呢? 我没有找到.......可能需要找到XML中字符串<mvc:interceptors>是怎么解析的...
不过这里已经得到了足够的信息,至少可以知道Interceptor读出来以后并没有进行什么排序,所以读出来的顺序应该就是List<MappedInterceptor> mappedInterceptors的顺序.
总结
简单总结一下
拦截器的流程:
请求 -> DispatcherServlet -> 各个拦截器定义顺序来执行前置拦截方法 -> 各个拦截器定义顺序倒序来执行后置拦截方法 -> 处理视图 -> 拦截器定义倒序顺序回收资源
RequestMappingHandlerMapping:
找到所有MappedInterceptor拦截器并设置到属性里.
找到handler方法并设置到属性里.
当有请求到DispatcherServlet的时候把2者结合成HandlerExecutionChain丢给DispatcherServlet去调用.
SpringMVC学习记录5的更多相关文章
- springMVC学习记录1-使用XML进行配置
SpringMVC是整个spring中的一个很小的组成,准确的说他是spring WEB这个模块的下一个子模块,Spring WEB中除了有springMVC还有struts2,webWork等MVC ...
- SpringMVC学习记录4
主题 SpringMVC有很多很多的注解.其中有2个注解@SessionAttributes @ModelAttribute我平时一般不用,因为实在是太灵活了.但是又有一定限制,用不好容易错.. 最近 ...
- SpringMVC学习记录3
这次的主题 最近一直在学习SpringMVC..(这句话我已经至少写了3,4遍了....).这次的研究主要是RequestMappingHandlerAdapter中的各种ArgumentsResol ...
- SpringMVC学习记录2
废话 最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧... 分享一下最近的一些心得..是关于DispatcherServlet的 DispatcherServlet与Cont ...
- SpringMVC学习记录
1E)Spring MVC框架 ①Jar包结构: docs+libs+schema. 版本区别:核心包,源码包. SpringMVC文档学习: 学习三步骤: 1)是什么? 开源框架 2)做什么? IO ...
- springMVC学习记录2-使用注解配置
前面说了一下使用xml配置springmvc,下面再说说注解配置.项目如下: 业务很简单,主页和输入用户名和密码进行登陆的页面. 看一下springmvc的配置文件: <?xml version ...
- SpringMVC学习记录1
起因 以前大三暑假实习的时候看到公司用SpringMVC而不是Struts2,老司机告诉我SpringMVC各种方便,各种解耦. 然后我自己试了试..好像是蛮方便的.... 基本上在Spring的基础 ...
- springMVC学习记录3-拦截器和文件上传
拦截器和文件上传算是springmvc中比较高级一点的内容了吧,让我们一起看一下. 下面先说说拦截器.拦截器和过滤器有点像,都可以在请求被处理之前和请求被处理之到做一些额外的操作. 1. 实现Hand ...
- SpringMVC学习记录七——sjon数据交互和拦截器
21 json数据交互 21.1 为什么要进行json数据交互 json数据格式在接口调用中.html页面中较常用,json格式比较简单,解析还比较方便. 比如:webservi ...
随机推荐
- 终于将rsync-3.1.2配置成功,之外还挖掘了一些新的用法
1.为什么要用rsync: 有两台主机,开始准备做HA,考虑到工作量的问题,最终决定将重要文件进行同步即可. 找了一些同步的工具,rsync得到一致好评,速度快,消耗小等等. 2.接着找资料,最后选用 ...
- sql 优化
1.选择最有效率的表名顺序(只在基于规则的优化器中有效): oracle的解析器按照从右到左的顺序处理 from 子句中的表名,from子句中写在最后的表(基础表driving table)将被最先处 ...
- centos本地yum源安装
1.为DVD或U盘创建一个用于挂载的目录 [root@localhost ~]# mkdir /media/CentOS/ 2.查看DVD或U盘所在的路径 [root@localhost ~]# fd ...
- PHP代码
1 <html> <head> <meta http-equiv="content-type" content="text/h ...
- ActiveMQ笔记(5):JMX监控
系统上线运行后,及时监控报警是很必要的手段,对于ActiveMQ而言,主要监控的指标有:MQ本身的健康状况.每个队列的生产者数量.消费者数量.队列的当前消息数等. ActiveMQ支持JMX监控,使用 ...
- [LeetCode] Edit Distance 编辑距离
Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2 ...
- Ajax入门(三)
get和post请求 1,get方式: 在url地址后面以请求字符串(传递的get参数信息)形式传递数据. 例: aj.open('get','./03.php?name=3tu'); 在传递特 ...
- mysqld初探
一.简介 deamon是守护神的意思,表示守护进程.mysqld就是mysql的服务器端,就是基于socket的一个服务器端程序,它始终监听3306端口(默认端口).mysql是客户端程序. 安装my ...
- ubuntu系统虚拟机下共享文件夹
一般情况 1.安装: sudo apt-get install open-vm-dkms 2.挂载: sudo mount -t vmhgfs .host:/ /mnt/hgfs 用以上命令安 ...
- typedef
第一.四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如:char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, ...