(4)调用被请求的Action的执行方法。
execute.executeAction(request, response, mapping);
public void executeAction(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);//这个方法里具体的处
//理Action调用,包括调用Action前后调用拦截器(Interceptor);
}
以下是调用Dispatcher类中serviceAction()的源代码:
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping, context);//此方法首先创建了一个名称为extraContext的Map对象 //它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象(extraContext)中查找。 //如果request中已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入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"; //根据前封装的mapping的信息去找到请求处理的Action try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); //获取配置包装Configuration类,此时的config所有的信息在容器如tomcat启动时已经加载完成了。 Configuration config = configurationManager.getConfiguration(); //下面这一句是创建Action代理的核心,下面要祥细介绍 (1) ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); //如果mapping中的result属性已经有值了,就直接跳到Result处理类 if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { //这个方法是调用Action处理方法的核心了,包括调用Action前后调用拦截器(Interceptor);后面要祥细介绍了。(2) proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { ........ } catch (Exception e) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }
先来了解一下 , 创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
配置信息加载完成后(这里说的配置信息加载是在容器启动时已经加载完成了,现在只要取得config对象的引用也就能获得所有配置信息了),创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
注:ActionProxy和ActionProxyFactory都是接口,他们的默认实现类分别是DefaultActionProxy和DefaultActionProxyFactory,位于com.opensymphony.xwork2包下。
祥细介绍 (1)
调用了DefaultActionProxyFactory类#.createActionProxy(namespace, name, method, extraContext, true, false);方法,源代码如下
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) { ActionInvocation inv = new DefaultActionInvocation(extraContext, true); //创建了ActionInvocation,并注入extraContext的Map对象,里面包含了request,session,application,mapping的信息 container.inject(inv); return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);//创建ActionProxy对象 } //以下是创建ActionProxy对象的方法的实现 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);//创建ActionProxy实现类DefaultActionProxy container.inject(proxy); proxy.prepare();//这方法找到请求action的config并做了相关init工作 return proxy; } protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); //根据namespace, actionName去configuration中找到是否有对应的ActionConfig config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { config = unknownHandlerManager.handleUnknownAction(namespace, actionName); } if (config == null) { throw new ConfigurationException(getErrorMessage()); } resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: " + method + " for action " + actionName); } invocation.init(this);//下面祥细分析 } finally { UtilTimerStack.pop(profileKey); } }
需要要先说明 一下关于Configuration config = configurationManager.getConfiguration();
在容器启动时先加载Struts2的配置文件,如果没有人为配置,则默认加载struts-default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的类中。
类com.opensymphony.xwork2.config.providers.XmlConfigurationProvider负责配置文件的读取和解析, addAction()方法负责读取<action>标签,并将数据保存在ActionConfig中;
addResultTypes()方法负责将<result-type>标签转化为ResultTypeConfig对象;
loadInterceptors()方法负责将<interceptor>标签转化为InterceptorConfi对象;
loadInterceptorStack()方法负责将<interceptor-ref>标签转化为InterceptorStackConfig对象;
loadInterceptorStacks()方法负责将<interceptor-stack>标签转化成InterceptorStackConfig对象。
而上面的方法最终会被addPackage()方法调用,将所读取到的数据汇集到PackageConfig对象中,要清楚明白这是容器启动时这工作,现在说的是struts处理请求的情况了。
所以根据namespace, actionName去configuration中找到是否有对应的ActionConfig
getActionConfig(namespace, actionName);在前面说mapping中的namespace时已经分析过了,如不清楚可以回前面看看。
invocation.init(this);是调用了DefaultActionInvocation的方法,在这方法里根据前面的信息,创建了一个请求的Action处理o
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了,如果有actionEventListener,则触发监听器的调用。 if (pushAction) { stack.push(action); contextMap.put("action", action); } invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); //把config加载的iterceptors放到一个新的ArrayList<InterceptorMapping>里 List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator(); } //createAction()方法的源代码 protected void createAction(Map<String, Object> contextMap) { // load action String timerKey = "actionCreate: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); //请看下面action的具体实现 action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch (Exception e) { ................... } finally { UtilTimerStack.pop(timerKey); } //如果有actionEventListener,则触发监听器的调用#prepare(action, stack)。 if (actionEventListener != null) { action = actionEventListener.prepare(action, stack); } } //通过反射创建了一个新的Action了,看到这应该很清楚Action的出生了吧。 public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception { Class clazz = getClassInstance(className); Object obj = clazz.newInstance(); if (injectInternal) { injectInternalBeans(obj); } return obj; }
创建Action完成了,现再往下一步就是 proxy.execute();真正地去调用action的方法了。跟进去祥细介绍如下:
执行的是StrutsActionProxy类的execute()方法,调用invocation.invoke();
public String execute() throws Exception { ActionContext previous = ActionContext.getContext(); ActionContext.setContext(invocation.getInvocationContext()); try { return invocation.invoke();//核心方法调用 } finally { if (cleanupContext) ActionContext.setContext(previous); } }
最终还是回到了DefaultActionInvocation类执行invoke()方法,在DefaultActionInvocation类中,
定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的execute()方法。
先来看一下Interceptor接口的定义:
Interceptor.java publicinterface Interceptor extends Serializable { void destroy(); void init(); String intercept(ActionInvocation invocation) throws Exception; }
所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用invocation.invoke(),
代码DefaultActionInvocation.invoke()方法的部分代码
if (interceptors.hasNext()) { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);}会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归。
DefaultActionInvocation.invoke()源代码如下:
public String invoke() throws Exception { String profileKey = "invoke: "; try { UtilTimerStack.push(profileKey); if (executed) { throw new IllegalStateException("Action has already executed"); } /* 下面代码说明。整个方法主要由2个if从句分割,在(1)处的if从句中,主要实现了拦截器的"递归"调用, 说它是递归调用,其实是一种非传统的递归。传统的递归应该是函数调用自身,最后达成一定条件后退出, 但是这里是将自身的引用作为参数传递给intercept(),然后在intercept()内部再调用DefaultActionInvocation的invoke(), 实现了递归调用。 */ if (interceptors.hasNext()) { final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); String interceptorMsg = "interceptor: " + interceptor.getName(); UtilTimerStack.push(interceptorMsg); try { resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); } finally { UtilTimerStack.pop(interceptorMsg); } } else { //执行完所有的拦截器后,调用这个方法里最终通过反射 methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);调用了action中的方法了。 resultCode = invokeActionOnly(); //最后resultCode=(String) methodResult; } //执行完action方法并返回结果后,此处调用了在PreResultListener中的定义的一些执行Result前的操作 if (!executed) { if (preResultListeners != null) { 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(); //// 根据配置文件构建Result,现在有以下这几种Result的实现,如:ActionChainResult,ServletDispatcherResult, //FreemarkerResult,ServletRedirectResult,ServletActionRedirectResult,VelocityResult等。 } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } } private void executeResult() throws Exception { result = createResult(); String timerKey = "executeResult: " + getResultCode(); try { UtilTimerStack.push(timerKey); if (result != null) { result.execute(this);//执行了result实现类的方法,表示对于请求的资源继续往下派发给别的去处理, //还是用相关的视图呈现给浏览器用户,这要看你配置的result-type, //以后有机会再一一分析各种result的处理流程程了。 } else if(){。。。。。。。。} } } finally { UtilTimerStack.pop(timerKey); } }
拦截器的调用活动图:
最后再说下有些朋友很关注struts2是在什么时候给action的各个属性setAttribute()的呢?其实这个过程是在调用ParametersInterceptor拦截器时
在执行doIntercept()中完成set值的。源码如下
public String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); if (!(action instanceof NoParameters)) { ActionContext ac = invocation.getInvocationContext(); final Map<String, Object> parameters = retrieveParameters(ac); if (LOG.isDebugEnabled()) { LOG.debug("Setting params " + getParameterLogMap(parameters)); } if (parameters != null) { Map<String, Object> contextMap = ac.getContextMap(); try { ReflectionContextState.setCreatingNullObjects(contextMap, true); ReflectionContextState.setDenyMethodExecution(contextMap, true); ReflectionContextState.setReportingConversionErrors(contextMap, true); ValueStack stack = ac.getValueStack(); setParameters(action, stack, parameters);//这是方法里就通过反射给Action setAttribute的。此处不再一一说明了,知道原理就行了。 } finally { ReflectionContextState.setCreatingNullObjects(contextMap, false); ReflectionContextState.setDenyMethodExecution(contextMap, false); ReflectionContextState.setReportingConversionErrors(contextMap, false); } } } return invocation.invoke(); }
呵呵,先分析到这里的,如果朋友有什么地方还不清楚的,可以指出,我会尽我最大努力为你解决。让我们共同学习共同进步。
相关推荐
Struts2的设计思路和工作流程与Struts1.x有很大的区别,这使得深入理解其源代码变得至关重要。 在分析Struts2的源代码之前,你需要首先获取Struts2的源代码,可以通过访问...
struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码
尽管描述中提到不包含XWork源代码,但XWork是Struts2的基础,它处理Action的执行和异常管理。 在Struts2.3.4源代码中,我们可以深入理解以下关键知识点: 1. **FilterDispatcher**: 这是Struts2框架的入口点,负责...
在分析"struts源代码源代码源代码"这个标题时,我们可以推断出讨论的主题是关于Struts框架的源码。Struts 2是其最新版本,基于Action和拦截器的模型,提供了灵活的控制流和强大的插件架构。源代码的学习可以帮助我们...
李兴华是一位知名的IT教育专家,他在讲解Struts2.0源代码时,通常会深入解析框架的核心机制、工作流程以及如何通过源码理解其实现原理。在“mldn 李兴华 struts2.0 源代码”这个主题中,我们可以学习到以下几个关键...
"strut2课程源代码第一天及说明"可能包含了逐步的教程,指导你从零开始搭建和运行一个简单的Struts2应用。 标签"struts2例子代码"表明这些源代码包含了具体的操作示例,比如Action类的编写、配置文件的设置、拦截器...
struts2 源代码 struts2 源代码 struts2 源代码 struts2 源代码
这个"Struts2源代码项目"显然包含了Struts2框架的源代码,这对于开发者深入理解其内部工作原理,以及进行定制化开发和调试是极其有价值的。 首先,我们来看看`.classpath`和`.project`这两个文件。它们是Eclipse ...
学习和研究JavaEE源代码及Struts2源代码,有助于开发者深入理解Web应用的工作原理,提升解决问题的能力,同时也能为开发更复杂的企业级应用打下坚实的基础。通过对源代码的阅读和分析,我们可以学习到最佳实践,提高...
这个源代码包提供了对Struts2核心组件、拦截器、插件以及其他相关模块的深入理解。 在Struts2的核心组件中,主要包括以下部分: 1. **Action类**:它是业务逻辑的载体,处理用户请求并返回结果。在Struts2中,...
"张孝祥Struts视频源代码"很可能是由知名IT讲师张孝祥制作的一系列关于Struts框架的教程,其中包含了相应的源代码,帮助学习者通过实际操作理解Struts的工作原理和应用。 在深入探讨Struts框架之前,我们首先需要...
深入研究Struts 1.3.10源代码,可以帮助我们掌握Web应用开发的基本架构,理解MVC模式在实践中的应用,以及学习如何通过配置和编程控制请求流程。同时,通过分析源码,还可以学习到如何处理HTTP请求,验证用户输入,...
讲述Struts2的工作原理。struts2源代码分析(个人觉得非常经典),讲解全面且易懂。