Struts2 源码分析——调结者(Dispatcher)之action请求
章节简言 |
上一章笔者讲到关于struts2启动的时候加载对应的准备工作。如加载配置文件struts.xml之类的信息。而相应的这些操作都离不开Dispatcher类的帮助。如果读者只是认为Dispatcher类的作用只有这些。那真的是大错特错了。所以本章笔者将继续讲到关于Dispatcher类的另一个功能。即是StrutsPrepareFilter类俩项工作中的处理request请求相关信息。在讲解之前,笔者还是想把相关的信息回想一下:当项目启动的时候,strtus2也就启动了。然后就会去初始化对应需要的信息(这部分内容上一章已经讲到了)。之后当用户在网址上输入访问的URL的时候。就会进入StrutsPrepareFilter类处理request请求的功能。好了。明白是什么到这一步就可以了。因为下面就是要讲到关于这一部分的内容。
调结者的action请求 |
StrutsPrepareFilter类在处理request请求的时候,需要用到一个叫PrepareOperations类的帮忙。PrepareOperations类可以说是StrutsPrepareFilter类和Dispatcher类的中间人。PrepareOperations类大部分的工作都是通过Dispatcher类完成的。先让我们看一段代码。如下
StrutsPrepareFilter类:
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();//用于初始化相关的功能操作。你可以理解为工具类一样子。
Dispatcher dispatcher = null;//这个类相当的重要。他的作用连接着StrutsExecuteFilter。这里可以命名为调结者。
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);//这里可以理解为把filterConfig在进行封装FilterHostConfig更为主便操作和理解。
init.initLogging(config);//获取名为loggerFactory的参数,并实例化这个类。一般为去用户自定义日志。
dispatcher = init.initDispatcher(config);//初化调结者。这里是重要。 prepare = new PrepareOperations(dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);//加载排除在内的action的正则表达式 postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
从上面的红色代码我们可以看出来,PrepareOperations类在实例化时候,接受Dispatcher类作为构造函数的参数。即是在struts2启动加载准备工作之后初始化。那么PrepareOperations类到底又做哪些工作呢?让我们在看一下下面的代码。如下
StrutsPrepareFilter类:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object());
} else {
10 prepare.setEncodingAndLocale(request, response);//设置请求的格式编码。
11 prepare.createActionContext(request, response);//action的上下文
12 prepare.assignDispatcherToThread();//把Dispatcher放入本地线程里面。
13 request = prepare.wrapRequest(request);
14 prepare.findActionMapping(request, response);//找到action映射的信息
}
chain.doFilter(request, response);
} finally {
prepare.cleanupRequest(request);
}
}
上面代码我们可以看出PrepareOperations类总共做了五件事情。先让笔者简单的讲解一下:当用户的request请求过来的时候,会判断一下request请求是不是被排除之外的。如果是,则把REQUEST_EXCLUDED_FROM_ACTION_MAPPING常量作为KEY,object实例作为值存放在request的Attrbute里面。这是为后面的StrutsExecuteFilter类作准备(下一章笔者会讲到)。如果不是,则进行request请求处理。如下
1.设置request请求的本地化和格式编码。实现上还是Dispatcher类在做工作。代码如下
PrepareOperations类:
/**
* 设置本地化和请求格式编码
*
* @param request servlet request
* @param response servlet response
*/
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
dispatcher.prepare(request, response);
}
Dispatcher类:
/**
* 设置请求的本地化和格式编码
*
* @param request
* The request
* @param response
* The response
*/
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
if (defaultEncoding != null) {
encoding = defaultEncoding;
} if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
encoding = "UTF-8";
} Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
} if (encoding != null) {
applyEncoding(request, encoding);
} if (locale != null) {
response.setLocale(locale);
} if (paramsWorkaroundEnabled) {
request.getParameter("foo"); // simply read any parameter (existing
// or not) to "prime" the request
}
}
2.创建Action上下文(ActionContext类),并把上下文存放在本地线程(ThreadLocal)。所谓的上下文可以理解为把相同性质的业务归为一类。而上下文就是这一类和外部相交处。所有的数据操作都可以通过他还完成。当然上下文的定义在不同的地方有不同的意思。请读者自行找阅资料。先让我们看一下代码。如下
PrepareOperations类:
/**
* 创建Action的上下文 ,并存在到本地线程
*
* @param request servlet request
* @param response servlet response
*
* @return the action context
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
} ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// 有旧的action上下文,我们可以认为有可能是跳转。
20 ctx = new ActionContext(new HashMap<>(oldContext.getContextMap()));
} else {
22 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();//从容器中获得值栈工厂,并新建值栈。
23 stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//创建上下MAP并合到值栈里面去。
24 ctx = new ActionContext(stack.getContext());//用值栈的上下MAP来新建上下文
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
27 ActionContext.setContext(ctx);
return ctx;
}
从上面的红色代码我们就能够看出第一次request请求就会创建一个新的action上下文(ActionContext)。同时也能明白ActionContext里面存放大量的关于request请求对应的数据。而这些操作又离不开Dispatcher类的帮忙。最后把上下文(ActionContext类)存放到ActionContext类内部的本地线程(ThreadLocal)。代码ActionContext.setContext(ctx)就是最好的说明。另外,上面有一点让笔者一直不明白为什么这么做。觉得没有什么意义。即是获得request的属性为CLEANUP_RECURSION_COUNTER的值,然后进行计算操作的功能。笔者不得不将他理解为:用于计算request请求跳转action的次数。就是一个request请求的生命周期通过了几个action请求。如果不对的话,请读者自行屏蔽。
3.把Dispatcher实例分配置到他内部的本地线程(ThreadLocal)。这一步主要是为了后面的StrutsExecuteFilter类的工作。让我们看一下代码吧。如下
PrepareOperations类:
/**
* dispatcher分配到Dispatcher类的本地线程
*/
public void assignDispatcherToThread() {
Dispatcher.setInstance(dispatcher);
}
4.把HttpServletRequest包装为对应的StrutsRequestWrapper或是MultiPartRequestWrapper。主要是为了方便开发人员操作处理multipart而以。这里比较简单。笔者不想过的解释。
5.找到对应action映射信息(ActionMapping类)。这部分的工作笔者认为是比较重要的。因为他将是StrutsExecuteFilter类工作的核心点。那么ActionMapping类又是什么呢?他是struts.xml配置文件上的action信息。有了他struts2才能知道当前请求是哪一个action类。那么相关的知识笔者会在后面的章节讲到。让我们看一下代码。如下
PrepareOperations类:
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} catch (Exception ex) {
dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
} return mapping;
}
当笔者看到红色代码的时候,不得不说一句。看!又离不开Dispatcher类。同时值得注意是的ActionMapper类。所有的struts.xml配置文件的action信息都在这个类上面。即是可以通过ActionMapper类找到对应的action映射信息(ActionMapping)。从而找到对应的action类(用户定义的action)。另外代码request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping)这边所做的事情。功能意思大家都能看得出来。那为什么这么做。主要还是因为后面的StrutsExecuteFilter类要用到。
从上面的五个事件中。笔者至少知道一点。Dispatcher类的工作真的很重要。而PrepareOperations类大部分只是一个中间人而以。当然这是笔者自己的理解。
Dispatcher的结束处理 |
笔者本来想把这个知识点做一个章节来讲。可是想太少了。为什么是结束工作呢?不管是struts2启动的准备工作。还是启动成功后的action请求工作。struts2都会在工作结束之进行一些处理。先看一下struts2启动的准备工作成完之后的处理。如下
StrutsPrepareFilter类的init方法:
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
Dispatcher类:
public void cleanUpAfterInit() {
if (LOG.isDebugEnabled()) {
LOG.debug("Cleaning up resources used to init Dispatcher");
}
ContainerHolder.clear();
}
InitOperations类:
public void cleanup() {
ActionContext.setContext(null);
}
从上面的代码中我们可以看一个叫ContainerHolder类。这个类主要是用于存放Container容器。而上面在struts2启动加载相关信息的准备工作结束之后。把Container容器给册除了。同时也去掉了上下文(ActionContext类)。
让我们看一下request请求结束后做了什么。如下
StrutsPrepareFilter类的doFilter方法:
prepare.cleanupRequest(request);
PrepareOperations类:
public void cleanupRequest(HttpServletRequest request) {
Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (counterVal != null) {
counterVal -= 1;
request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
if (counterVal > 0 ) {
LOG.debug("skipping cleanup counter={}", counterVal);
return;
}
}
// always clean up the thread request, even if an action hasn't been executed
try {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.setContext(null);
Dispatcher.setInstance(null);
devModeOverride.remove();
}
}
Dispatcher类:
public void cleanUpRequest(HttpServletRequest request) {
ContainerHolder.clear();
if (!(request instanceof MultiPartRequestWrapper)) {
return;
}
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
multiWrapper.cleanUp();
}
从上面的红色代码我们知道他在request请求结束之后,把Container容器给册除了。本地线程的Dispatcher类的实例删除了。上下文(ActionContext类)删除了。
到了这里笔者就明白了一点:
1.struts2启动的时候,加载相关的配置信息。然后生成Dispatcher类的实例,初始化Container容器并把Dispatcher类的实例存放在PrepareOperations类里面。那么struts2的启动准备工作结束,启动成功。这时在把对应生成的Container容器和上下文(ActionContext类)删除掉。
注意:上下文(ActionContext类)在这个时候可能是没有生成的。但他做了删除的工作。
2.启动成功之后。用户开始请求action。这个时候struts2会初始化一个新的Container容器和上下文(ActionContext类),分配Dispatcher类的实例到本地线程(ThreadLocal)中,找到对应的request请求的action映射(ActionMapping)并开始处理用户对应的action请求。action请求成功之后,会删除对应的Container容器、本地线程的Dispatcher类的实例、上下文(ActionContext类)。即是一个请求,一个Container容器,一个上下文(ActionContext类)。一个本地线程的Dispatcher类的实例。
注意:删除Dispatcher类的实例是本地线程的。而不是PrepareOperations类的实例。(读者不要搞错了。然后一直会去想:删除了Dispatcher类的实例。又在哪里创建了。不好意思。启动的时候就创建,之后就在也没有了。)
本章总结 |
关于StrutsPrepareFilter类的工作。还有Dispatcher的作用。相信读者看到这里的时候,心里都会有一个大概念的想法。当然笔者并非会专业的写书者。所以可能有些读者或多或少很难去理解笔者的意思。请见谅。关于调结者(Dispatcher)之俩章,主要就是想让读者明白Dispatcher做了什么。和StrutsPrepareFilter类有什么关系。为后面学习StrutsExecuteFilter类的工作做准备。
Struts2 源码分析——调结者(Dispatcher)之action请求的更多相关文章
- Struts2 源码分析——调结者(Dispatcher)之执行action
章节简言 上一章笔者写关于Dispatcher类如何处理接受来的request请求.当然读者们也知道他并非正真的执行action操作.他只是在执行action操作之前的准备工作.那么谁才是正真的执行a ...
- Struts2 源码分析——调结者(Dispatcher)之准备工作
章节简言 上一章笔者讲到关于struts2过滤器(Filter)的知识.让我们了解到StrutsPrepareFilter和StrutsExecuteFilter的作用.特别是StrutsPrepar ...
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
- Struts2 源码分析——配置管理之ContainerProvider接口
本章简言 上一章笔者讲到关于Dispatcher类的执行action功能,知道了关于执行action需要用到的信息.而本章将会讲到的内容也跟Dispatcher类有关系.那就是配置管理中的Contai ...
- Struts2 源码分析——过滤器(Filter)
章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...
- Struts2 源码分析——配置管理之PackageProvider接口
本章简言 上一章讲到关于ContainerProvider的知识.让我们知道struts2是如何注册相关的数据.也知道如何加载相关的配置信息.本章笔者将讲到如何加载配置文件里面的package元素节点 ...
- Struts2 源码分析——Hello world
新建第一个应用程序 上一章我们讲到了关于struts2核心机制.对于程序员来讲比较概念的一章.而本章笔者将会亲手写一个Hello world的例子.所以如果对struts2使用比较了解的朋友,请跳过本 ...
- Struts2 源码分析——DefaultActionInvocation类的执行action
本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...
- Struts2 源码分析——拦截器的机制
本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...
随机推荐
- 【TJOI&HEOI2016】【Bzoj4551】树
这道题是可以用树链剖分来做的,但其实有比它更加简单的做法--并查集. 可以想到,这类题的一种常见做法是离线处理,先全部读入,再从后往前处理,每次遇到标记操作,就把这个点的标记次数减一,到零以后就把这个 ...
- asp.net 生成图形验证码(字母和数字混合)
验证码技术是网站开发过程中比较重要的技术,可以防止非法人员利用注册机或者登陆工具来攻击我们的网站.下面是效果图: 具体实现方法如下: 1.主要思路是:引用Using System.Drawing命名空 ...
- 跟服务器交互的登录Demo
服务器写死 账号密码,演示登录 服务器代码: 开发工具MyEclipse public class LoginServlet extends HttpServlet { /** * The doGet ...
- SharePoint 2010 Survey的Export to Spreadsheet功能怎么不见了?
背景信息: 最近用户报了一个问题,说他创建的Survey里将结果导出成Excel文件(Export to spreadsheet)的按钮不见了. 原因排查: 正常情况下,这个功能只存在于SharePo ...
- The easy way to implement a Red-Black tree
Red-Black trees are notorious for being nightmares of pointer manipulation. Instructors will show th ...
- 细嗅Promise
读完这篇文章,预计会消耗你 40 分钟的时间. Ajax 出现的时候,刮来了一阵异步之风,现在 Nodejs 火爆,又一阵异步狂风刮了过来.需求是越来越苛刻,用户对性能的要求也是越来越高,随之而来的是 ...
- SQL Server AlwaysOn
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/高性能解决方案 概述 环境: 域服务器:windows server 2008 R2 SP1,192.168.2.10 DNS:1 ...
- [ASP.NET MVC 小牛之路]02 - C#知识点提要
本人博客已转移至:http://www.exblr.com/liam 本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性.对于正在 ...
- OracleConnection is obsolete
用EF搞Oracle的 fake CodeFirst 时,一直报错以下错误: 对类型“System.Data.OracleClient.OracleConnection”的存储区提供程序实例调用“ge ...
- JQuery图片切换动画效果
由于博主我懒,所以页面画的比较粗糙,但是没关系,因为我主要讲的是如何实现图片动画切换. 思路:想必大家都逛过淘宝或者其他的一些网站,一般都会有图片动画切换的效果,那是怎样实现的呢?博主我呢,技术不是很 ...