Action是Strus2的核心逻辑之一。但这并不意味着Action是复杂的,实际上Action很简单,一个普通的java类就可以作为一个Action。我们之所以可以很方便的使用Action,是因为Struts2的框架对Action做了最大的支持。就像是一个老大,手底下有一大群牛逼的小弟,所以可以整天无所事事。Action的逻辑简单,但是围绕着Action的各个组件的逻辑就复杂的不行了。上图展示了struts2中一个从请求的获取到响应的流程。
一个请求被获取后,会经过层层的Filter到达ActionMapper。ActionMapper可以判断该是否需要为该请求调用一个Action。如果是的话,这个请求会被送给ActionProxy。ActionProxy会通过配置文件和请求生成一个ActionInvocation。ActionInvocation的主要任务是根据运行对应的Action方法,并获取Result。在ActionInvocation中会先运行Interceptor,如果没有从Interceptor中获取Result,则会运行Action的方法。获取result后,会检查配置文件中Action对应的结果,并根据结果访问页面或者请求另外一个Action(Action链)。
Action可以通过三种方式实现:
1.一个类可以是普通的java类,但是必须有一个execute()
2.实现了Action接口,Action接口提供了execute()方法
3.继承了ActionSupport类型,ActionSupport实现了Action和其他的一些接口,提供了许多常用方法。
proxy应当是Action流程的开端,所以这里从Proxy开始进行分析一直到获取Result为止。
DefaultActionProxy的execute中首先会设置ActionContext,将当前请求的Context给予ActionContext。每个ActionContext都是基于ThreadLocal且静态的。也就说每个请求都会生成一个ActionContext,这个ActionContext在此次请求是可以全局调用的
接下来DefaultActionInvocation的invoke方法被调用了。在invoke方法中首先会查看拦截器,这些拦截器被存储在名为interceptors的列表中。遍历这个链表,并运行拦截器的interceptor方法,将invocation本身作为参数传递给interceptor方法。interceptor方法的最后会重新调用invocation的invoke方法,所以该方法是递归方法。拦截器会不断递归interceptor方法,直到所有的拦截器都被调用过或者在一个拦截器中获得了一个resultCode。
invoke方法代码:
public String invoke() throws Exception { String profileKey = "invoke: "; try { ... 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(); } ... if (!executed) { if (preResultListeners != null) { for (Object preResultListener : preResultListeners) { PreResultListener listener = (PreResultListener) preResultListener; ... try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } if (proxy.getExecuteResult()) { executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
如果运行完所有拦截器都没有获取ResultCode,那么就会运行请求指定的Action的方法了。该方法是通过反射机制获取的。
通过反射机制获取方法代码:
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception { String methodName = proxy.getMethod(); ... try { UtilTimerStack.push(timerKey); boolean methodCalled = false; Object methodResult = null; Method method = null; try { method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { ... } if (!methodCalled) { methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY); } return saveResult(actionConfig, methodResult); } catch (NoSuchMethodException e) { ... } catch (InvocationTargetException e) { ... } finally { UtilTimerStack.pop(timerKey); } }
获取到一个ResultCode后会检查PreResultListener,PreResultListener通常也是拦截器,但是不会运行interceptor方法,而是beforeResult方法。
最后会调用DefaultActionInvocation的executeResult方法。这个方法的作用是根据我们获取的resultcode,在配置中获取一个Result对象,并同过Result对象的execute方法将响应结果发送出去。
Action的请求参数一般可以通过“注入”的方式获取,这种方式避免了Action与ServletAPI的耦合,但是也有很多情况是需要直接访问ServletAPI获取的。Struts2也提供了许多访问ServletAPI的方法。
ActionContext类提供了HttpServletRequest,HttpSession,ServletContext的获取方法,它们分别对应JSP内置对象的request,session,application。但是ActionContext只能获取request内容,也就是只能从页面获取参数。
ActionContext的获取方式为 :
ActionContext actionContext = ActionContext.getContext();
ActionContext是action执行时的上下文,它存放着Action需要用到的各种对象.如:请求参数(Parameter),会话(Session),Servlet上下文(ServletContext),本地化(Locale)
每次执行Action的时候都会创建新的ActionContext,ActionContext是线程安全的,在同一个线程里ActionContext是唯一的。
ActionContext对Servlet的一些内容进行了封装,从而使Action与Serlvet解耦合。
ServletActionContext继承于ActionContext,提供了一些直接访问Serlvet的方法。SerlvetActionContext可以操作Cookie.ServletActionContext可以获取的对象有:
HttpServletRequest——请求对象
HttpServletResponse——响应对象
ServletContext——上下文信息
ApplicationContext——Http页面上下文
ActionContext与ServletActionContext都是基于WEB应用的,所以普通的java应用是无法获取ActionContext或ServletActionContext的。在运行的时候,它们的值将为NULL。ActionContext和ServletActionContext都可以获取WEB数据,但是ActionContext更偏向于值得操作而ServletActionContext更偏向Serlvet的操作。
SessionAware,RequestAware,ApplicationAware,ParameterAware也可以访问页面获取的数据。要使用这些工具,Action就必须实现SessionAware,RequestAware,ApplicationAware,ParameterAware接口。并实现
Public void setSession(Map<String,Object> map);
Public void setRequest(Map<String,Object> map );
Public void setApplication(Map<String,Object> map);
Public void setParameter(Map<String,Object> map);
ServletRequestAware,ServletResponseAware可以用来获取HttpServletRequest和HttpServletResponse对象。要获取这些对象,就必须实现ServletRequestAware,ServletResponseAware接口。并实现:
Public void setServletRequest(HttpServletRequest request);
Public void setServletResponse(HttpServletResponse response);
参考了许多资料,也看了点代码,不过老实说还是一知半解,望高手出来指点迷津:)