Struts2 源码分析-----工作原理分析
请求过程
struts2 架构图如下图所示:
依照上图,我们可以看出一个请求在struts的处理大概有如下步骤:
1、客户端初始化一个指向servlet容器(例如tomcat)的请求;
2、这个请求经过一系列的过滤器(filter)(这些过滤器中有一个叫做actioncontextcleanup的可选过滤器,这个过滤器对于struts2和其他框架的集成很有帮助,例如:sitemesh plugin);
3、接着strutsprepareandexecutefilter被调用,strutsprepareandexecutefilter询问actionmapper来决定这个请求是否需要调用某个action;
4、如果actionmapper决定需要调用某个action,filterdispatcher把请求的处理交给actionproxy;
5、actionproxy通过configuration manager询问框架的配置文件,找到需要调用的action类;
6、actionproxy创建一个actioninvocation的实例。
7、actioninvocation实例使用命名模式来调用,在调用action的过程前后,涉及到相关拦截器(intercepter)的调用。
8、一旦action执行完毕,actioninvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个action链)一个需要被表示的jsp或者freemarker的模版。在表示的过程中可以使用struts2 框架中继承的标签。在这个过程中需要涉及到actionmapper。
9、接着按照相反次序执行拦截器链 ( 执行 action 调用之后的部分 )。最后,响应通过滤器链返回(过滤器技术执行流程与拦截器一样,都是先执行前面部分,后执行后面部)。如果过滤器链中存在 actioncontextcleanup,filterdispatcher 不会清理线程局部的 actioncontext。如果不存在 actioncontextcleanup 过滤器,filterdispatcher 会清除所有线程局部变量。
strut2源码分析
首先我们使用struts2框架都会在web.xml中注册和映射struts2,配置内容如下:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.strutsprepareandexecutefilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
注:在早期的struts2中,都是使用filterdispathcer,从struts 2.1.3开始,它已不推荐使用。如果你使用的struts的版本 >= 2.1.3,推荐升级到新的filter,strutsprepareandexecutefilter。在此研究的是strutsprepareandexecutefilter。
strutsprepareandexecutefilter中的方法:
void init(filterconfig filterconfig) | 继承自filter,过滤器的初始化 |
dofilter(servletrequest req, servletresponse res, filterchain chain) | 继承自filter,执行过滤器 |
void destroy() | 继承自filter,用于资源释放 |
void postinit(dispatcher dispatcher, filterconfig filterconfig) | callback for post initialization(一个空的方法,用于方法回调初始化) |
web容器一启动,就会初始化核心过滤器strutsprepareandexecutefilter,并执行初始化方法,初始化方法如下:
public void init(filterconfig filterconfig) throws servletexception { initoperations init = new initoperations(); dispatcher dispatcher = null; try { //封装filterconfig,其中有个主要方法getinitparameternames将配置文件中的初始化参数名字以string格式存储在list中 filterhostconfig config = new filterhostconfig(filterconfig); //初始化struts内部日志 init.initlogging(config); //创建dispatcher ,并初始化 dispatcher = init.initdispatcher(config); init.initstaticcontentloader(config, dispatcher); //初始化类属性:prepare 、execute prepare = new prepareoperations(filterconfig.getservletcontext(), dispatcher); execute = new executeoperations(filterconfig.getservletcontext(), dispatcher); this.excludedpatterns = init.buildexcludedpatternslist(dispatcher); //回调空的postinit方法 postinit(dispatcher, filterconfig); } finally { if (dispatcher != null) { dispatcher.cleanupafterinit(); } init.cleanup(); } }
关于封装filterconfig,首先看下filterhostconfig ,源码如下:
public class filterhostconfig implements hostconfig { private filterconfig config; //构造方法 public filterhostconfig(filterconfig config) { this.config = config; } //根据init-param配置的param-name获取param-value的值 public string getinitparameter(string key) { return config.getinitparameter(key); } //返回初始化参数名的迭代器 public iterator<string> getinitparameternames() { return makeiterator.convert(config.getinitparameternames()); } //返回servlet上下文 public servletcontext getservletcontext() { return config.getservletcontext(); } }
接下来,看下strutsprepareandexecutefilter中init方法中dispatcher = init.initdispatcher(config);这是初始化dispatcher的。
public dispatcher initdispatcher( hostconfig filterconfig ) { dispatcher dispatcher = createdispatcher(filterconfig); dispatcher.init(); return dispatcher; }
创建dispatcher,会读取 filterconfig 中的配置信息,将配置信息解析出来,封装成为一个map,然后根绝servlet上下文和参数map构造dispatcher :
private dispatcher createdispatcher( hostconfig filterconfig ) { //存放参数的map map<string, string> params = new hashmap<string, string>(); //将参数存放到map for ( iterator e = filterconfig.getinitparameternames(); e.hasnext(); ) { string name = (string) e.next(); string value = filterconfig.getinitparameter(name); params.put(name, value); } //根据servlet上下文和参数map构造dispatcher return new dispatcher(filterconfig.getservletcontext(), params); }
这样dispatcher对象创建完成,接着就是dispatcher对象的初始化,打开dispatcher类,看到它的init方法如下:
public void init() { if (configurationmanager == null) { configurationmanager = createconfigurationmanager(beanselectionprovider.default_bean_name); } try { init_filemanager(); //加载org/apache/struts2/default.properties init_defaultproperties(); //加载struts-default.xml,struts-plugin.xml,struts.xml init_traditionalxmlconfigurations(); init_legacystrutsproperties(); //用户自己实现的configurationproviders类 init_customconfigurationproviders(); //filter的初始化参数 init_filterinitparameters() ; init_aliasstandardobjects() ; container container = init_preloadconfiguration(); container.inject(this); init_checkweblogicworkaround(container); if (!dispatcherlisteners.isempty()) { for (dispatcherlistener l : dispatcherlisteners) { l.dispatcherinitialized(this); } } } catch (exception ex) { if (log.iserrorenabled()) log.error("dispatcher initialization failed", ex); throw new strutsexception(ex); } }
这里主要是加载一些配置文件的,将按照顺序逐一加载:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……
现在,我们回到strutsprepareandexecutefilter类中,刚才我们分析了strutsprepareandexecutefilter类的init方法,该方法在web容器一启动就会调用的,当用户访问某个action的时候,首先调用核心过滤器strutsprepareandexecutefilter的dofilter方法,该方法内容如下:
public void dofilter(servletrequest req, servletresponse res, filterchain chain) throws ioexception, servletexception { httpservletrequest request = (httpservletrequest) req; httpservletresponse response = (httpservletresponse) res; try { //设置编码和国际化 prepare.setencodingandlocale(request, response); //创建action上下文 prepare.createactioncontext(request, response); prepare.assigndispatchertothread(); if (excludedpatterns != null && prepare.isurlexcluded(request, excludedpatterns)) { chain.dofilter(request, response); } else { request = prepare.wraprequest(request); actionmapping mapping = prepare.findactionmapping(request, response, true); //如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action if (mapping == null) { boolean handled = execute.executestaticresourcerequest(request, response); if (!handled) { chain.dofilter(request, response); } } else { //执行action execute.executeaction(request, response, mapping); } } } finally { prepare.cleanuprequest(request); } }
下面对dofilter方法中的重点部分一一讲解:
(1)prepare.setencodingandlocale(request, response)
public void setencodingandlocale(httpservletrequest request, httpservletresponse response) { dispatcher.prepare(request, response); }
这方法里面我们可以看到它只是调用了dispatcher的prepare方法而已,下面我们看看dispatcher的prepare方法:
string encoding = null; if (defaultencoding != null) { encoding = defaultencoding; } // check for ajax request to use utf-8 encoding strictly http://www.w3.org/tr/xmlhttprequest/#the-send-method 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 } }
我们可以看到该方法只是简单的设置了encoding 和locale ,做的只是一些辅助的工作。
(2)prepare.createactioncontext(request, response)
我们回到strutsprepareandexecutefilter的dofilter方法,看到第10行代码:prepare.createactioncontext(request, response);这是action上下文的创建,actioncontext是一个容器,这个容易主要存储request、session、application、parameters等相关信 息.actioncontext是一个线程的本地变量,这意味着不同的action之间不会共享actioncontext,所以也不用考虑线程安全问 题。其实质是一个map,key是标示request、session、……的字符串,值是其对应的对象,我们可以看到com.opensymphony.xwork2.actioncontext类中时如下定义的:
static threadlocal<actioncontext> actioncontext = new threadlocal<actioncontext>();
我们看下prepareoperations类的createactioncontext方法:
public void prepare(httpservletrequest request, httpservletresponse response) { /** * creates the action context and initializes the thread local */ 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; } //此处是从threadlocal中获取此actioncontext变量 actioncontext oldcontext = actioncontext.getcontext(); if (oldcontext != null) { // detected existing context, so we are probably in a forward ctx = new actioncontext(new hashmap<string, object>(oldcontext.getcontextmap())); } else { valuestack stack = dispatcher.getcontainer().getinstance(valuestackfactory.class).createvaluestack(); stack.getcontext().putall(dispatcher.createcontextmap(request, response, null, servletcontext)); //stack.getcontext()返回的是一个map<string,object>,根据此map构造一个actioncontext ctx = new actioncontext(stack.getcontext()); } request.setattribute(cleanup_recursion_counter, counter); //将actioncontext存到threadlocal actioncontext.setcontext(ctx); return ctx; }
上面第18行代码中dispatcher.createcontextmap,如何封装相关参数:
public map<string,object> createcontextmap(httpservletrequest request, httpservletresponse response, actionmapping mapping, servletcontext context) { // request map wrapping the http request objects map requestmap = new requestmap(request); // parameters map wrapping the http parameters. actionmapping parameters are now handled and applied separately map params = new hashmap(request.getparametermap()); // session map wrapping the http session map session = new sessionmap(request); // application map wrapping the servletcontext map application = new applicationmap(context); //requestmap、params、session等map封装成为一个上下文map map<string,object> extracontext = createcontextmap(requestmap, params, session, application, request, response, context); if (mapping != null) { extracontext.put(servletactioncontext.action_mapping, mapping); } return extracontext; }
(3)request = prepare.wraprequest(request)
我们再次回到strutsprepareandexecutefilter的dofilter方法中,看到第15行:request = prepare.wraprequest(request);这一句是对request进行包装的,我们看下prepare的wraprequest方法:
public httpservletrequest wraprequest(httpservletrequest oldrequest) throws servletexception { httpservletrequest request = oldrequest; try { // wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding (ww-1278) request = dispatcher.wraprequest(request, servletcontext); } catch (ioexception e) { throw new servletexception("could not wrap servlet request with multipartrequestwrapper!", e); } return request; }
我们看下dispatcher的wraprequest:
public httpservletrequest wraprequest(httpservletrequest request, servletcontext servletcontext) throws ioexception { // don't wrap more than once if (request instanceof strutsrequestwrapper) { return request; } string content_type = request.getcontenttype(); //如果content_type是multipart/form-data类型,则将request包装成multipartrequestwrapper对象,否则包装成strutsrequestwrapper对象 if (content_type != null && content_type.contains("multipart/form-data")) { multipartrequest mpr = getmultipartrequest(); localeprovider provider = getcontainer().getinstance(localeprovider.class); request = new multipartrequestwrapper(mpr, request, getsavedir(servletcontext), provider); } else { request = new strutsrequestwrapper(request, disablerequestattributevaluestacklookup); } return request; }
此次包装根据请求内容的类型不同,返回不同的对象,如果为multipart/form-data类型,则返回multipartrequestwrapper类型的对象,该对象服务于文件上传,否则返回strutsrequestwrapper类型的对象,multipartrequestwrapper是strutsrequestwrapper的子类,而这两个类都是httpservletrequest接口的实现。
(4)actionmapping mapping = prepare.findactionmapping(request, response, true)
包装request后,通过actionmapper的getmapping()方法得到请求的action,action的配置信息存储在actionmapping对象中,如strutsprepareandexecutefilter的dofilter方法中第16行:actionmapping mapping = prepare.findactionmapping(request, response, true);我们找到prepare对象的findactionmapping方法:
public actionmapping findactionmapping(httpservletrequest request, httpservletresponse response, boolean forcelookup) { //首先从request对象中取mapping对象,看是否存在 actionmapping mapping = (actionmapping) request.getattribute(struts_action_mapping_key); //不存在就创建一个 if (mapping == null || forcelookup) { try { //首先创建actionmapper对象,通过actionmapper对象创建mapping对象 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, servletcontext, httpservletresponse.sc_internal_server_error, ex); } } return mapping; }
下面是actionmapper接口的实现类defaultactionmapper的getmapping()方法的源代码:
public actionmapping getmapping(httpservletrequest request, configurationmanager configmanager) { actionmapping mapping = new actionmapping(); //获得请求的uri,即请求路径url中工程名以后的部分,如/useraction.action string uri = geturi(request); //修正url的带;jsessionid 时找不到的bug int indexofsemicolon = uri.indexof(";"); uri = (indexofsemicolon > -1) ? uri.substring(0, indexofsemicolon) : uri; //删除扩展名,如.action或者.do uri = dropextension(uri, mapping); if (uri == null) { return null; } //从uri中分离得到请求的action名、命名空间。 parsenameandnamespace(uri, mapping, configmanager); //处理特殊的请求参数 handlespecialparameters(request, mapping); //如果允许动态方法调用,即形如/useraction!getall.action的请求,分离action名和方法名 return parseactionname(mapping); }
下面对getmapping方法中的重要部分一一讲解:
①:parsenameandnamespace(uri, mapping, configmanager)
我们主要看下第14行的parsenameandnamespace(uri, mapping, configmanager);这个方法的主要作用是分离出action名和命名空间:
protected void parsenameandnamespace(string uri, actionmapping mapping, configurationmanager configmanager) { string namespace, name; int lastslash = uri.lastindexof("/"); //最后的斜杆的位置 if (lastslash == -1) { namespace = ""; name = uri; } else if (lastslash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. namespace = "/"; name = uri.substring(lastslash + 1); //允许采用完整的命名空间,即设置命名空间是否必须进行精确匹配 } else if (alwaysselectfullnamespace) { // simply select the namespace as everything before the last slash namespace = uri.substring(0, lastslash); name = uri.substring(lastslash + 1); } else { // try to find the namespace in those defined, defaulting to "" configuration config = configmanager.getconfiguration(); string prefix = uri.substring(0, lastslash); //临时的命名空间,将会用来进行匹配 namespace = "";//将命名空间暂时设为"" boolean rootavailable = false;//rootavailable作用是判断配置文件中是否配置了命名空间"/" // find the longest matching namespace, defaulting to the default for (object cfg : config.getpackageconfigs().values()) { //循环遍历配置文件中的package标签 string ns = ((packageconfig) cfg).getnamespace(); //获取每个package标签的namespace属性 //进行匹配 if (ns != null && prefix.startswith(ns) && (prefix.length() == ns.length() || prefix.charat(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootavailable = true; } } name = uri.substring(namespace.length() + 1); // still none found, use root namespace if found if (rootavailable && "".equals(namespace)) { namespace = "/"; } } if (!allowslashesinactionnames) { int pos = name.lastindexof('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } //将分离后的acion名和命名空间保存到mapping对象 mapping.setnamespace(namespace); mapping.setname(cleanupactionname(name)); }
看到上面代码的第14行,参数alwaysselectfullnamespace我们可以通过名字就能大概猜出来"允许采用完整的命名空间",即设置命名空间是否必须进行精确匹配,true必须,false可以模糊匹配,默认是false。进行精确匹配时要求请求url中的命名空间必须与配置文件中配置的某个命名空间必须相同,如果没有找到相同的则匹配失败。这个参数可通过struts2的"struts.mapper.alwaysselectfullnamespace"常量配置,如:
<constant name="struts.mapper.alwaysselectfullnamespace" value="true" />
当alwaysselectfullnamespace为true时,将uri以lastslash为分割,左边的为namespace,右边的为name。如:http://localhost:8080/myproject/home/actionname!method.action,此时uri为/home/actionname!method.action(不过前面把后缀名去掉了,变成/home/actionname!method),lastslash的,当前值是5,这样namespace为"/home", name为actionname!method。
②:parseactionname(mapping)
我们看到18行:return parseactionname(mapping);主要是用来处理形如/useraction!getall.action的请求,分离action名和方法名:
protected actionmapping parseactionname(actionmapping mapping) { if (mapping.getname() == null) { return null; } //如果允许动态方法调用 if (allowdynamicmethodcalls) { // handle "name!method" convention. string name = mapping.getname(); int exclamation = name.lastindexof("!"); //如果包含"!"就进行分离 if (exclamation != -1) { //分离出action名 mapping.setname(name.substring(0, exclamation)); //分离出方法名 mapping.setmethod(name.substring(exclamation + 1)); } } return mapping; }
到此为止getmapping方法已经分析结束了!
(5)execute.executeaction(request, response, mapping)
上面我们分析完了mapping的获取,继续看dofilter方法:
//如果mapping为空,则认为不是调用action,会调用下一个过滤器链 if (mapping == null) { //执行请求css,js文件。并返回是否成功。 boolean handled = execute.executestaticresourcerequest(request, response); if (!handled) { chain.dofilter(request, response); } } else { //执行action execute.executeaction(request, response, mapping); }
如果mapping对象不为空,则会执行action
public void executeaction(httpservletrequest request, httpservletresponse response, actionmapping mapping) throws servletexception { dispatcher.serviceaction(request, response, servletcontext, mapping); }
我们可以看到它里面只是简单的调用了dispatcher的serviceaction方法:我们找到dispatcher的serviceaction方法:
public void serviceaction(httpservletrequest request, httpservletresponse response, actionmapping mapping) throws servletexception { //封转上下文环境,主要将requestmap、params、session等map封装成为一个上下文map map<string, object> extracontext = createcontextmap(request, response, mapping); //如果之前没有值栈,就从actioncontext中先取出值栈,放入extracontext valuestack stack = (valuestack) request.getattribute(servletactioncontext.struts_valuestack_key); boolean nullstack = stack == null; if (nullstack) { actioncontext ctx = actioncontext.getcontext(); if (ctx != null) { stack = ctx.getvaluestack(); } } if (stack != null) { extracontext.put(actioncontext.value_stack, valuestackfactory.createvaluestack(stack)); } string timerkey = "handling request from dispatcher"; try { utiltimerstack.push(timerkey); string namespace = mapping.getnamespace();//获得request请求里面的命名空间,即是struts.xml是的package节点元素 string name = mapping.getname();//获得request请求里面的action名 string method = mapping.getmethod();//要执行action的方法 actionproxy proxy = getcontainer().getinstance(actionproxyfactory.class).createactionproxy(namespace, name, method, extracontext, true, false);//获得action的代理 request.setattribute(servletactioncontext.struts_valuestack_key, proxy.getinvocation().getstack()); // 如果action映射是直接就跳转到网页的话, if (mapping.getresult() != null) { result result = mapping.getresult(); result.execute(proxy.getinvocation()); } else { proxy.execute();//这里就是执行action } if (!nullstack) { request.setattribute(servletactioncontext.struts_valuestack_key, stack); } } catch (configurationexception e) { logconfigurationexception(request, e); senderror(request, response, httpservletresponse.sc_not_found, e); } catch (exception e) { if (handleexception || devmode) { senderror(request, response, httpservletresponse.sc_internal_server_error, e); } else { throw new servletexception(e); } } finally { utiltimerstack.pop(timerkey); } }
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类:
public actionproxy createactionproxy(string namespace, string actionname, string methodname, map<string, object> extracontext, boolean executeresult, boolean cleanupcontext) { actioninvocation inv = createactioninvocation(extracontext, true); container.inject(inv); return createactionproxy(inv, namespace, actionname, methodname, executeresult, cleanupcontext); }
dispatcher类是重要的调结者,defaultactioninvocation类是执行action类实例的行动者。而action代理类(actionproxy类)则是他们之间的中间人。相当于dispatcher类通过action代理类(actionproxy类)命令defaultactioninvocation类去执行action类实例。
defaultactionproxyfactory类:
public actionproxy createactionproxy(actioninvocation inv, string namespace, string actionname, string methodname, boolean executeresult, boolean cleanupcontext) { defaultactionproxy proxy = new defaultactionproxy(inv, namespace, actionname, methodname, executeresult, cleanupcontext); container.inject(proxy); proxy.prepare(); return proxy; }
defaultactionproxy类:
protected void prepare() { string profilekey = "create defaultactionproxy: "; try { utiltimerstack.push(profilekey); config = configuration.getruntimeconfiguration().getactionconfig(namespace, actionname);//根据空间命名和action名来找到对应的配置信息 if (config == null && unknownhandlermanager.hasunknownhandlers()) { config = unknownhandlermanager.handleunknownaction(namespace, actionname); } if (config == null) { throw new configurationexception(geterrormessage()); } resolvemethod();//找到对应的方法名。 if (config.isallowedmethod(method)) { invocation.init(this); } else { throw new configurationexception(preparenotallowederrormessage()); } } finally { utiltimerstack.pop(profilekey); } }
1.获得actionconfig类实例。并通过actionconfig类实例找到对应的方法名。actionconfig类就是存放配置文件里面的action元素节点的信息。
2.实初始化defaultactioninvocation类的实例。即是根据actionproxy类实例找到对应的action类实例(用户自己定义的类)。
defaultactionproxy类:
private void resolvemethod() { // 从配置中获得方法名。如果还是空的话,就用默认的值。即是"execute"方法。 if (stringutils.isempty(this.method)) { this.method = config.getmethodname(); if (stringutils.isempty(this.method)) { this.method = actionconfig.default_method; } methodspecified = false; } }
defaultactioninvocation类:
public void init(actionproxy proxy) { this.proxy = proxy; map<string, object> contextmap = createcontextmap(); // setting this so that other classes, like object factories, can use the actionproxy and other // contextual information to operate actioncontext actioncontext = actioncontext.getcontext(); if (actioncontext != null) { actioncontext.setactioninvocation(this); } createaction(contextmap);//找到对应的action类实例 if (pushaction) { stack.push(action); contextmap.put("action", action); } invocationcontext = new actioncontext(contextmap); invocationcontext.setname(proxy.getactionname()); createinterceptors(proxy); }
看了代码就能清楚的知道一件事情。如果我们在struts.xml配置文件里面action元素节点里面没有指定方法的时候,就用会默认的方法。即是execute方法。而关于init方法就能明确明白为了找到action类并实例他。init方法里面调用了俩个非重要的方法。一个是用于新建action类实例的方法createaction。一个是用于获得相关拦截器的方法createinterceptors。看一下代码吧。
defaultactioninvocation类:
protected void createaction(map<string, object> contextmap) { // load action string timerkey = "actioncreate: " + proxy.getactionname(); try { utiltimerstack.push(timerkey); action = objectfactory.buildaction(proxy.getactionname(), proxy.getnamespace(), proxy.getconfig(), contextmap); } catch (instantiationexception e) { throw new xworkexception("unable to instantiate action!", e, proxy.getconfig()); } catch (illegalaccessexception e) { throw new xworkexception("illegal access to constructor, is it public?", e, proxy.getconfig()); } catch (exception e) { string gripe; if (proxy == null) { gripe = "whoa! no actionproxy instance found in current actioninvocation. this is bad ... very bad"; } else if (proxy.getconfig() == null) { gripe = "sheesh. where'd that actionproxy get to? i can't find it in the current actioninvocation!?"; } else if (proxy.getconfig().getclassname() == null) { gripe = "no action defined for '" + proxy.getactionname() + "' in namespace '" + proxy.getnamespace() + "'"; } else { gripe = "unable to instantiate action, " + proxy.getconfig().getclassname() + ", defined for '" + proxy.getactionname() + "' in namespace '" + proxy.getnamespace() + "'"; } gripe += (((" -- " + e.getmessage()) != null) ? e.getmessage() : " [no message in exception]"); throw new xworkexception(gripe, e, proxy.getconfig()); } finally { utiltimerstack.pop(timerkey); } if (actioneventlistener != null) { action = actioneventlistener.prepare(action, stack); } }
defaultactioninvocation类:
protected void createinterceptors(actionproxy proxy) { // get a new list so we don't get problems with the iterator if someone changes the original list list<interceptormapping> interceptorlist = new arraylist<>(proxy.getconfig().getinterceptors()); interceptors = interceptorlist.iterator(); }
action代理类(actionproxy类)的准备工作完成之后,就开始执行了。最顶部的代码中就很明确的看的出来(serviceaction方法)。先是根据参数mapping来判断是否为直接回返。如果不是才去执行action代理类(actionproxy类)的execute方法。这便是action代理类(actionproxy类)的主要工作。即是执行action请求。那么让我们看一下action代理类(actionproxy类)的execute方法源码吧。
public string execute() throws exception { actioncontext nestedcontext = actioncontext.getcontext(); actioncontext.setcontext(invocation.getinvocationcontext()); string retcode = null; string profilekey = "execute: "; try { utiltimerstack.push(profilekey); retcode = invocation.invoke(); } finally { if (cleanupcontext) { actioncontext.setcontext(nestedcontext); } utiltimerstack.pop(profilekey); } return retcode; }
从红色的代码部分我们就知道就是去执行defaultactioninvocation类实例的invoke方法
defaultactioninvocation类:
public string invoke() throws exception { string profilekey = "invoke: "; try { utiltimerstack.push(profilekey); if (executed) { throw new illegalstateexception("action has already executed"); } if (interceptors.hasnext()) {//获得一个拦截器 final interceptormapping interceptor = interceptors.next(); string interceptormsg = "interceptor: " + interceptor.getname(); utiltimerstack.push(interceptormsg); try { resultcode = interceptor.getinterceptor().intercept(defaultactioninvocation.this);//执行拦截器 } finally { utiltimerstack.pop(interceptormsg); } } else { resultcode = invokeactiononly(); } // this is needed because the result will be executed, then control will return to the interceptor, which will // return above and flow through again if (!executed) { if (preresultlisteners != null) { log.trace("executing preresultlisteners for result [{}]", result); for (object preresultlistener : preresultlisteners) { preresultlistener listener = (preresultlistener) preresultlistener; string _profilekey = "preresultlistener: "; try { utiltimerstack.push(_profilekey); listener.beforeresult(this, resultcode); } finally { utiltimerstack.pop(_profilekey); } } } // now execute the result, if we're supposed to if (proxy.getexecuteresult()) { executeresult(); } executed = true; } return resultcode; } finally { utiltimerstack.pop(profilekey); } }
上面的红色的代码是这个方法的核心点之一。让我们看一下红色代码做什么?判断interceptors是否有拦截器。如果没有就直接执行invokeactiononly方法。即是执行action类实例对应的方法。如果有就获得拦截器并执行拦截器(执行intercept方法)。好了。关键点就在这个执行拦截器身上。即是执行intercept方法。intercept方法有一个参数就是defaultactioninvocation类的接口。这个参数让struts2的aop思想能够进行。为什么这样子讲呢?不清楚读者有没有想过。为什么这边判断拦截器是用if而不是用for 或是 while呢?必竟拦截器不只一个。我们都清楚aop的目标就是让业务模块选择对应的切面。那么就有可能存在多个拦截器。这也是为什么亮点的原因了。看一下拦截器的代码就知道了。如下
推荐博客
logginginterceptor类:
public string intercept(actioninvocation invocation) throws exception { logmessage(invocation, start_message); string result = invocation.invoke(); logmessage(invocation, finish_message); return result; }
拦截器开始的时候,执行相关的拦截器逻辑,然后又重新调用defaultactioninvocation类的invoke方法。从而获得下一个拦截器。就是这样子下一个拦截器又开始执行自己的intercept方法。做了相关的拦截器逻辑之后。又一次重新调用defaultactioninvocation类的invoke方法。又做了相似的工作。只到没有了拦截器,执行用户action类实例的方法并返回结果。有了结果之后,就开始续继执行当前上一个拦截器的后半部分代码。直到返回到最开始的拦截器执行后半部分的代码。