章节简言 |
上一章笔者讲到关于 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 类:
- 1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
- ServletException {
- 2 3 HttpServletRequest request = (HttpServletRequest) req;
- 4 HttpServletResponse response = (HttpServletResponse) res;
- 5 6
- try {
- 7
- if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
- 8 request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object());
- 9
- } 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映射的信息
- 15
- }
- 16 chain.doFilter(request, response);
- 17
- } finally {
- 18 prepare.cleanupRequest(request);
- 19
- }
- 20
- }
上面代码我们可以看出 PrepareOperations 类总共做了五件事情。先让笔者简单的讲解一下:当用户的 request 请求过来的时候,会判断一下 request 请求是不是被排除之外的。如果是,则把 REQUEST_EXCLUDED_FROM_ACTION_MAPPING 常量作为 KEY,object 实例作为值存放在 request 的 Attrbute 里面。这是为后面的 StrutsExecuteFilter 类作准备 (下一章笔者会讲到)。如果不是,则进行 request 请求处理。如下
1. 设置 request 请求的本地化和格式编码。实现上还是 Dispatcher 类在做工作。代码如下
PrepareOperations 类:
- 1
- /**
- 2 * 设置本地化和请求格式编码
- 3 *
- 4 * @param request servlet request
- 5 * @param response servlet response
- 6 */
- 7 public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
- 8 dispatcher.prepare(request, response);
- 9
- }
Dispatcher 类:
- 1
- /**
- 2 * 设置请求的本地化和格式编码
- 3 *
- 4 * @param request
- 5 * The request
- 6 * @param response
- 7 * The response
- 8 */
- 9 public void prepare(HttpServletRequest request, HttpServletResponse response) {
- 10 String encoding = null;
- 11
- if (defaultEncoding != null) {
- 12 encoding = defaultEncoding;
- 13
- }
- 14 15
- if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
- 16 encoding = "UTF-8";
- 17
- }
- 18 19 Locale locale = null;
- 20
- if (defaultLocale != null) {
- 21 locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
- 22
- }
- 23 24
- if (encoding != null) {
- 25 applyEncoding(request, encoding);
- 26
- }
- 27 28
- if (locale != null) {
- 29 response.setLocale(locale);
- 30
- }
- 31 32
- if (paramsWorkaroundEnabled) {
- 33 request.getParameter("foo"); // simply read any parameter (existing
- 34 // or not) to "prime" the request
- 35
- }
- 36
- }
2. 创建 Action 上下文 (ActionContext 类), 并把上下文存放在本地线程 (ThreadLocal)。所谓的上下文可以理解为把相同性质的业务归为一类。而上下文就是这一类和外部相交处。所有的数据操作都可以通过他还完成。当然上下文的定义在不同的地方有不同的意思。请读者自行找阅资料。先让我们看一下代码。如下
PrepareOperations 类:
- 1
- /**
- 2 * 创建Action的上下文 ,并存在到本地线程
- 3 *
- 4 * @param request servlet request
- 5 * @param response servlet response
- 6 *
- 7 * @return the action context
- 8 */
- 9 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
- 10 ActionContext ctx;
- 11 Integer counter = 1;
- 12 Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
- 13
- if (oldCounter != null) {
- 14 counter = oldCounter + 1;
- 15
- }
- 16 17 ActionContext oldContext = ActionContext.getContext();
- 18
- if (oldContext != null) {
- 19 // 有旧的action上下文,我们可以认为有可能是跳转。
- 20 ctx = new ActionContext(new HashMap < >(oldContext.getContextMap()));
- 21
- } 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来新建上下文
- 25
- }
- 26 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
- 27 ActionContext.setContext(ctx);
- 28
- return ctx;
- 29
- }
从上面的红色代码我们就能够看出第一次 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 类:
- 1
- /**
- 2 * dispatcher分配到Dispatcher类的本地线程
- 3 */
- 4 public void assignDispatcherToThread() {
- 5 Dispatcher.setInstance(dispatcher);
- 6
- }
4. 把 HttpServletRequest 包装为对应的 StrutsRequestWrapper 或是 MultiPartRequestWrapper。主要是为了方便开发人员操作处理 multipart 而以。这里比较简单。笔者不想过的解释。
5. 找到对应 action 映射信息 (ActionMapping 类)。这部分的工作笔者认为是比较重要的。因为他将是 StrutsExecuteFilter 类工作的核心点。那么 ActionMapping 类又是什么呢?他是 struts.xml 配置文件上的 action 信息。有了他 struts2 才能知道当前请求是哪一个 action 类。那么相关的知识笔者会在后面的章节讲到。让我们看一下代码。如下
PrepareOperations 类:
- 1 public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
- 2 ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
- 3
- if (mapping == null || forceLookup) {
- 4
- try {
- 5 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
- 6
- if (mapping != null) {
- 7 request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
- 8
- }
- 9
- } catch(Exception ex) {
- 10 dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
- 11
- }
- 12
- }
- 13 14
- return mapping;
- 15
- }
当笔者看到红色代码的时候,不得不说一句。看!又离不开 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 方法:
- 1
- if (dispatcher != null) {
- 2 dispatcher.cleanUpAfterInit();
- 3
- }
- 4 init.cleanup();
Dispatcher 类:
- 1 public void cleanUpAfterInit() {
- 2
- if (LOG.isDebugEnabled()) {
- 3 LOG.debug("Cleaning up resources used to init Dispatcher");
- 4
- }
- 5 ContainerHolder.clear();
- 6
- }
InitOperations 类:
- 1 public void cleanup() {
- 2 ActionContext.setContext(null);
- 3
- }
从上面的代码中我们可以看一个叫 ContainerHolder 类。这个类主要是用于存放 Container 容器。而上面在 struts2 启动加载相关信息的准备工作结束之后。把 Container 容器给册除了。同时也去掉了上下文 (ActionContext 类)。
让我们看一下 request 请求结束后做了什么。如下
StrutsPrepareFilter 类的 doFilter 方法:
- 1 prepare.cleanupRequest(request);
PrepareOperations 类:
- 1 public void cleanupRequest(HttpServletRequest request) {
- 2 Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
- 3
- if (counterVal != null) {
- 4 counterVal -= 1;
- 5 request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
- 6
- if (counterVal > 0) {
- 7 LOG.debug("skipping cleanup counter={}", counterVal);
- 8
- return;
- 9
- }
- 10
- }
- 11 // always clean up the thread request, even if an action hasn't been executed
- 12
- try {
- 13 dispatcher.cleanUpRequest(request);
- 14
- } finally {
- 15 ActionContext.setContext(null);
- 16 Dispatcher.setInstance(null);
- 17 devModeOverride.remove();
- 18
- }
- 19
- }
Dispatcher 类:
- 1 public void cleanUpRequest(HttpServletRequest request) {
- 2 ContainerHolder.clear();
- 3
- if (! (request instanceof MultiPartRequestWrapper)) {
- 4
- return;
- 5
- }
- 6 MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
- 7 multiWrapper.cleanUp();
- 8
- }
从上面的红色代码我们知道他在 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 类的工作做准备。
来源: http://www.cnblogs.com/hayasi/p/5832578.html