源码分析——Action代理类的工作
Action代理类的新建 |
通过《Struts2 源码分析——调结者(Dispatcher)之执行action》章节我们知道执行action请求,最后会落到Dispatcher类的serviceAction方法上面。可惜笔者并没有在这一章里面对他自己详细的讲解。先让我们看一下代码吧?知道他在做什么吧。如下
Dispatcher类:
1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
2 throws ServletException {
3
4 Map<String, Object> extraContext = createContextMap(request, response, mapping);
5
6 //如果之前就有了值栈,就是新建一个新的值栈,放入extraContext
7 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
8 boolean nullStack = stack == null;
9 if (nullStack) {
10 ActionContext ctx = ActionContext.getContext();
11 if (ctx != null) {
12 stack = ctx.getValueStack();
13 }
14 }
15 if (stack != null) {
16 extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
17 }
18
19 String timerKey = "Handling request from Dispatcher";
20 try {
21 UtilTimerStack.push(timerKey);
22 String namespace = mapping.getNamespace();//获得request请求里面的命名空间,即是struts.xml是的package节点元素
23 String name = mapping.getName();//获得request请求里面的action名
24 String method = mapping.getMethod();//要执行action的方法
25
26 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name,
27 method, extraContext, true, false);//获得action的代理
28
29 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
30
31 // 如果action映射是直接就跳转到网页的话,
32 if (mapping.getResult() != null) {
33 Result result = mapping.getResult();
34 result.execute(proxy.getInvocation());
35 } else {
36 proxy.execute();//这里就是执行action
37 }
38
39
40 //
41 if (!nullStack) {
42 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
43 }
44 } catch (ConfigurationException e) {
45 logConfigurationException(request, e);
46 sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
47 } catch (Exception e) {
48 if (handleException || devMode) {
49 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
50 } else {
51 throw new ServletException(e);
52 }
53 } finally {
54 UtilTimerStack.pop(timerKey);
55 }
56 }
1.根据传入的参数request, response, mapping来新建一个上下文Map。上下文Map就是一个存了关于RequestMap类,SessionMap类,ApplicationMap类等实例。即是request请求相关的信息,只是把他变成了对应的MAP类而以。
2.从request请求中找到对应的值栈(ValueStack)。如果没有就新建值栈。然后存放到上下文Map里面,对应的KEY为ActionContext.VALUE_STACK常量的值。即是"com.opensymphony.xwork2.util.ValueStack.ValueStack"。
3.从Mapping参数中提取对应的request请求的命名空间,action名字和方法名。
4.从Container容器中找到ActionProxyFactory类,并根据request请求的命名空间,action名字和方法名,上下文Map来获得对应的action代理类(ActionProxy)。然后更新request请求中的对应的值栈(ValueStack)。
5.根据Mapping参数来判断是否为直接输出结果。还是执行action代理类。
6.最后在判断之前是否request请求没有找到对应的值栈(ValueStack)。如果有找到值栈(ValueStack),则更新request请求中的对应的值栈(ValueStack)。
所以我们的目标很明确就是要去看一下action代理类(ActionProxy)。了解他到底做了什么。才能明白如何找到对应的action类,并执行对应的方法。从上面我们也知道action代理类的新建是通过ActionProxyFactory接口实例来进行的。即是DefaultActionProxyFactory类的实例。显然就是一个简章的工厂模式。让我们看一下新建action代理类的代码吧。
DefaultActionProxyFactory类:
1 public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
2
3 ActionInvocation inv = createActionInvocation(extraContext, true);
4 container.inject(inv);
5 return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
6 }
看到了吧。在新建action代理类的时候还要用到ActionInvocation接口的实例。即是DefaultActionInvocation类的实例。前面几章笔者曾经讲过Dispatcher类才是正真执行action类实例的人。这里笔者不得不在提一下。Dispatcher类是重要的调结者,DefaultActionInvocation类是执行action类实例的行动者。而action代理类(ActionProxy类)则是他们之间的中间人。相当于Dispatcher类通过action代理类(ActionProxy类)命令DefaultActionInvocation类去执行action类实例。
Action代理类的准备工作 |
action代理类(ActionProxy类)在命令DefaultActionInvocation类去执行action类实例之前,还是有做了一些准备工作。好吧。笔者还是希望通过代码来说话。看一下代码吧。
DefaultActionProxyFactory类:
1 public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
2
3 DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
4 container.inject(proxy);
5 proxy.prepare();
6 return proxy;
7 }
DefaultActionProxy类:
1 protected void prepare() {
2 String profileKey = "create DefaultActionProxy: ";
3 try {
4 UtilTimerStack.push(profileKey);
5 config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);//根据空间命名和action名来找到对应的配置信息
6
7 if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
8 config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
9 }
10 if (config == null) {
11 throw new ConfigurationException(getErrorMessage());
12 }
13
14 resolveMethod();//找到对应的方法名。
15
16 if (config.isAllowedMethod(method)) {
17 invocation.init(this);
18 } else {
19 throw new ConfigurationException(prepareNotAllowedErrorMessage());
20 }
21 } finally {
22 UtilTimerStack.pop(profileKey);
23 }
24 }
从上面的代码,我们可以看出在执行action类之前,大概做了俩件准备工作:
1.获得ActionConfig类实例。并通过ActionConfig类实例找到对应的方法名。ActionConfig类就是存放配置文件里面的action元素节点的信息。
2.实初始化DefaultActionInvocation类的实例。即是根据ActionProxy类实例找到对应的action类实例(用户自己定义的类)。
代码中俩个方法是笔者希望读者明白的。一个是DefaultActionProxy类的resolveMethod方法。一个是DefaultActionInvocation类的init方法。为什么要讲这俩个方法。上面的俩件事情主要的功能都是在这俩个方法里面。让我们看一代码吧?
DefaultActionProxy类:
1 private void resolveMethod() {
2 // 从配置中获得方法名。如果还是空的话,就用默认的值。即是"execute"方法。
3 if (StringUtils.isEmpty(this.method)) {
4 this.method = config.getMethodName();
5 if (StringUtils.isEmpty(this.method)) {
6 this.method = ActionConfig.DEFAULT_METHOD;
7 }
8 methodSpecified = false;
9 }
10 }
DefaultActionInvocation类:
1 public void init(ActionProxy proxy) {
2 this.proxy = proxy;
3 Map<String, Object> contextMap = createContextMap();
4
5 // Setting this so that other classes, like object factories, can use the ActionProxy and other
6 // contextual information to operate
7 ActionContext actionContext = ActionContext.getContext();
8
9 if (actionContext != null) {
10 actionContext.setActionInvocation(this);
11 }
12
13 createAction(contextMap);//找到对应的action类实例
14
15 if (pushAction) {
16 stack.push(action);
17 contextMap.put("action", action);
18 }
19
20 invocationContext = new ActionContext(contextMap);
21 invocationContext.setName(proxy.getActionName());
22
23 createInterceptors(proxy);
24 }
看了代码就能清楚的知道一件事情。如果我们在struts.xml配置文件里面action元素节点里面没有指定方法的时候,就用会默认的方法。即是execute方法。而关于init方法就能明确明白为了找到action类并实例他。init方法里面调用了俩个非重要的方法。一个是用于新建action类实例的方法createAction。一个是用于获得相关拦截器的方法createInterceptors。看一下代码吧。
DefaultActionInvocation类:
1 protected void createAction(Map<String, Object> contextMap) {
2 // load action
3 String timerKey = "actionCreate: " + proxy.getActionName();
4 try {
5 UtilTimerStack.push(timerKey);
6 action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
7 } catch (InstantiationException e) {
8 throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig());
9 } catch (IllegalAccessException e) {
10 throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
11 } catch (Exception e) {
12 String gripe;
13
14 if (proxy == null) {
15 gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
16 } else if (proxy.getConfig() == null) {
17 gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
18 } else if (proxy.getConfig().getClassName() == null) {
19 gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
20 } else {
21 gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
22 }
23
24 gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
25 throw new XWorkException(gripe, e, proxy.getConfig());
26 } finally {
27 UtilTimerStack.pop(timerKey);
28 }
29
30 if (actionEventListener != null) {
31 action = actionEventListener.prepare(action, stack);
32 }
33 }
DefaultActionInvocation类:
1 protected void createInterceptors(ActionProxy proxy) {
2 // Get a new List so we don't get problems with the iterator if someone changes the original list
3 List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
4 interceptors = interceptorList.iterator();
5 }
相信读者一定能看的懂代码吧。俩个方法中在笔者看来最重要的体现便是ObjectFactory类。ObjectFactory类是用于新一个实例的。上面的方法里面就是用ObjectFactory类来创建一个action类的实例。
好了。到了这里面action代理类(ActionProxy类)的准备工作算是做完了。让笔者理一下。准备工作完成之后。笔者至少知道action类实例有了。要执行的方法名也有了。要执行的拦截器也有了。有了这些信息难道strtus2会不知道去执行对应的工作吗?
Action代理类的主要工作 |
action代理类(ActionProxy类)的准备工作完成之后,就开始执行了。最顶部的代码中就很明确的看的出来(serviceAction方法)。先是根据参数mapping来判断是否为直接回返。如果不是才去执行action代理类(ActionProxy类)的execute方法。这便是action代理类(ActionProxy类)的主要工作。即是执行action请求。那么让我们看一下action代理类(ActionProxy类)的execute方法源码吧。
1 public String execute() throws Exception {
2 ActionContext nestedContext = ActionContext.getContext();
3 ActionContext.setContext(invocation.getInvocationContext());
4
5 String retCode = null;
6
7 String profileKey = "execute: ";
8 try {
9 UtilTimerStack.push(profileKey);
10
11 retCode = invocation.invoke();
12 } finally {
13 if (cleanupContext) {
14 ActionContext.setContext(nestedContext);
15 }
16 UtilTimerStack.pop(profileKey);
17 }
18
19 return retCode;
20 }
很好。从红色的代码部分我们就知道就是去执行DefaultActionInvocation类实例的invoke方法。DefaultActionInvocation类和action代理类(ActionProxy类)的关系看起相当的复杂。可以说是我中有你,你中有我。DefaultActionProxy类新建的时候需要DefaultActionInvocation类的实例。而DefaultActionInvocation类的实例初始化的时候,action代理类(ActionProxy类)的实例会传DefaultActionInvocation类里面并存放起来。即是在init方法的时候。值得注意的是这个时候的Action上下文(ActionContext类)有发生一件细微的变化。不是以前的了。而是从DefaultActionInvocation类的实例中得来的。cleanupContext参数表示要不要执行完成之后就清除掉当前的。把原来的放在去。最后回返结果。
本章总结 |
本章主要是讲到关于action代理类(ActionProxy类)的工作。知道了DefaultActionInvocation类是用去执行action类的行动者。而Dispatcher类是调结者。action代理类(ActionProxy类)是DefaultActionInvocation类和Dispatcher类的中间人。即是Dispatcher类通过action代理类(ActionProxy类)命令DefaultActionInvocation类去执行action类实例。
源码分析——Action代理类的工作的更多相关文章
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- JUC源码分析-其它工具类(一)ThreadLocalRandom
JUC源码分析-其它工具类(一)ThreadLocalRandom ThreadLocalRandom 是 JDK7 在 JUC 包下新增的随机数生成器,它解决了 Random 在多线程下多个线程竞争 ...
- 转:Ogre源码分析之Root类、Facade模式
Ogre源码分析(一)Root类,Facade模式 Ogre中的Root对象是一个Ogre应用程序的主入口点.因为它是整个Ogre引擎的外观(Façade)类.通过Root对象来开启和停止Ogre是最 ...
- HDFS源码分析心跳汇报之BPServiceActor工作线程运行流程
在<HDFS源码分析心跳汇报之数据结构初始化>一文中,我们了解到HDFS心跳相关的BlockPoolManager.BPOfferService.BPServiceActor三者之间的关系 ...
- Springboot源码分析之代理三板斧
摘要: 在Spring的版本变迁过程中,注解发生了很多的变化,然而代理的设计也发生了微妙的变化,从Spring1.x的ProxyFactoryBean的硬编码岛Spring2.x的Aspectj注解, ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- MyBatis 源码分析——动态代理
MyBatis框架是如何去执行SQL语句?相信不只是你们,笔者也想要知道是如何进行的.相信有上一章的引导大家都知道SqlSession接口的作用.当然默认情况下还是使用DefaultSqlSessio ...
- 【Canal源码分析】Sink及Store工作过程
一.序列图 二.源码分析 2.1 Sink Sink阶段所做的事情,就是根据一定的规则,对binlog数据进行一定的过滤.我们之前跟踪过parser过程的代码,发现在parser完成后,会把数据放到一 ...
随机推荐
- 【DB2】If 'db2' is not a typo you can run the following command to lookup the package that contains the binary: command-not-found db2 bash: db2: command not found
数据库安装以后,db2报错如下: If 'db2' is not a typo you can run the following command to lookup the package that ...
- 建立第一个wcf程序
使用管理员权限启动vs (否者将导致ServiceHost开启失败 权限不足) 1.创建一个空的控制台程序 2.添加程序集引用 System.ServiceModel 3.写入一些代码 如下 usin ...
- (转)ASP.NET MVC:Razor 引入命名空间
页面中引用 c# @using MvcApplication83.Models @using MvcApplication83.Common 行尾不需要加分号,加上也无妨(不过得全加上). VB.Ne ...
- 【软件project】——软工视频总结
软件project是一门研究用project化方法构建和维护有效的.有用的和高质量的软件的学科.它涉及程序设计语言.数据库.软件开发工具.系统平台.标准.设计模式等方面. 软工,基本的六阶段:制定计划 ...
- atitit.jQuery Validate验证框架详解与ati Validate 设计新特性
atitit.jQuery Validate验证框架详解与ati Validate 设计新特性 1. AtiValidate的目标1 2. 默的认校验规则1 2.1. 使用方式 1.metadata用 ...
- PHP系统学习1
1.php变量 2.php引用变量 $name1=&$name2; 3.全局变量 4.魔术变量__LINE__,__FILE__,__FUNCTION__,__CLASS__,__METHOD ...
- iOS7 SDK新特性
春风又绿加州岸.物是人非又一年.WWDC 2013 keynote落下帷幕,新的iOS开发旅程也由此开启.在iOS7界面重大变革的背后,开发人员们须要知道的又有哪些呢.同去年一样,我会先简单纵览地介绍 ...
- [python小记]使用lxml修改xml文件,并遍历目录
这次的目的是遍历目录,把目标文件及相应的目录信息更新到xml文件中.在经过痛苦的摸索之后,从python自带的ElementTree投奔向了lxml.而弃用自带的ElementTree的原因就是,na ...
- Apache: You don't have permission to access / on this server
当我们需要使用Apache配置虚拟主机时,有可能会出现这个问题:Apache: You don't have permission to access / on this server # 同IP不同 ...
- Powershell分支条件
Where-Object 进行条件判断很方便,如果在判断后执行很多代码可以使用IF-ELSEIF-ELSE语句.语句模板: If(条件满足){如果条件满足就执行代码}Else{如果条件不满足}条件判断 ...