相信熟悉 Struts1 的程序员,对 Struts2 会迷惑,凡事是是而非。我也曾经遇到了这种状况。Struts2 在设计的时候采用 webwork 的内核,尽可能按照 Struts1 的编码习惯。css
我不知道各位怎么学习 Struts1,当我阅读了核心控制器 org.apache.struts.action.ActionServlet 的源码后,感到对 Struts1 的工做机制豁然开朗。 Struts2 一样也是 MVC 框架,但核心控制器是过滤器 org.apache.struts2.dispatcher.FilterDispatcher。java
感谢网上对 Struts2 工做机制研究而且愿意跟你们分享的热心人,我在学习 Struts2 的时候获得不少帮助。若是你不是颇有经验的程序员,我说的不少东西你可能马上理解不了。若是有时间,我会作成 ppt,也但愿给你们讲解,共同交流进步。程序员
若是你须要亲自动手实践,学习源码,请下载如下的 2 个 jar 包。web
咱们从看官方的流程图开始。当本篇文章结束的时候,咱们会再一遍来看它。apache
初始的请求经过一条标准的过滤器链,到达 servlet 容器 ( 好比 tomcat 容器,WebSphere 容器 )。设计模式
过滤器链包括可选的 ActionContextCleanUp 过滤器,用于系统整合技术,如 SiteMesh 插件。tomcat
接着调用 FilterDispatcher,FilterDispatcher 查找 ActionMapper,以肯定这个请求是否须要调用某个 Action。session
若是 ActionMapper 肯定须要调用某个 Action,FilterDispatcher 将控制权交给 ActionProxy。数据结构
ActionProxy 依照框架的配置文件(struts.xml),找到须要调用的 Action 类。app
ActionProxy 建立一个 ActionInvocation 的实例。ActionInvocation 先调用相关的拦截器 (Action 调用以前的部分),最后调用 Action。
一旦 Action 调用返回结果,ActionInvocation 根据 struts.xml 配置文件,查找对应的转发路径。返回结果一般是(但不老是,也多是另外的一个 Action 链)JSP 技术或者 FreeMarker 的模版技术的网页呈现。Struts2 的标签和其余视图层组件,帮助呈现咱们所须要的显示结果。在此,我想说清楚一些,最终的显示结果必定是 HTML 标签。标签库技术和其余视图层技术只是为了动态生成 HTML 标签。
接着按照相反次序执行拦截器链 ( 执行 Action 调用以后的部分 )。最后,响应经过滤器链返回(过滤器技术执行流程与拦截器同样,都是先执行前面部分,后执行后面部)。若是过滤器链中存在 ActionContextCleanUp,FilterDispatcher 不会清理线程局部的 ActionContext。若是不存在 ActionContextCleanUp 过滤器,FilterDispatcher 会清除全部线程局部变量。
备注:拦截和过滤器的执行顺序可能一些人理解不了,我以生活中的范例说明。我去上海的 IBM 实验室出差,火车沿途停靠蚌埠,南京,最终达到上海。办完事情后回来,沿途的停靠站是南京、蚌埠。有没有注意到火车停靠站的顺序相反了。好,转到咱们遇到的技术问题,上海的业务至关于 Action 执行,是调用的真正目标。蚌埠和南京是两个分别的过滤器。即便我两次路过南京,只是一个过滤器的调用先执行一半后执行一半罢了。
传统的 Java MVC 设计模式,控制器自然是 servlet。也许有人说,没有 servlet 还叫 MVC 结构吗?对 filter 做为控制器表示怀疑。filter 为何不能够作控制器,动态网页也能够作控制器?我不知道若是你开发 PHP 项目,MVC 你怎么处理的,可是我认为是确定的。
请看下面的例子,过滤器实现控制器。核心方法 doFilter 的处理有 3 个出口。
请求资源以 .action 结尾,进行 action 处理
对样式表的直接访问。如地址栏直接输入网址 /css/main.css 将会被拒绝
其它资源请求,不处理请求直接经过
class FilterDispatcher implements Filter { private FilterConfig filterConfig; public void init(FilterConfig filterConfig) throws ServletException {} public void destroy() {} // 核心过滤方法 public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; String uri = req.getRequestURI(); // 1 action 请求 // 可能的 uri 形式为 / 站点名 /resourceName/ 可选路径 /Product_input.action if (uri.endsWith(".action")) { int lastIndex = uri.lastIndexOf("/"); //1.1 处理 action 结尾的请求 String action = uri.substring(lastIndex + 1); if (action.equals("Product_input.action")) { //1.1.1 请求商品输入不作处理 } else if (action.equals("Product_save.action")) { Product product = new Product(); //1.1.2 保存商品信息 product.setProductName(request.getParameter("productName")); product.setDescription(request.getParameter("description")); product.setPrice(request.getParameter("price")); product.save(); request.setAttribute("product", product); } //1.2 转向视图 String dispatchUrl = null; if (action.equals("Product_input.action")) { dispatchUrl = "/jsp/ProductForm.jsp"; } else if (action.equals("Product_save.action")) { dispatchUrl = "/jsp/ProductDetails.jsp"; } if (dispatchUrl != null) { RequestDispatcher rd = request .getRequestDispatcher(dispatchUrl); rd.forward(request, response); } } else if (uri.indexOf("/css/") != -1 && req.getHeader("referer") == null) { //2 拒绝对样式表的直接访问 res.sendError(HttpServletResponse.SC_FORBIDDEN); } else { //3 请求其余资源,经过过滤器 filterChain.doFilter(request, response); } } }
前面讲过 Struts2 的核心控制器为 filter,对于一个控制器,核心的生命周期方法有 3 个。
// 初始化,加载资源 public void init(FilterConfig filterConfig) throws ServletException // 销毁,回收资源 public void destroy() // 过滤 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
分别讲解 FilterDispatcher 3 个方法
init 方法:初始化过滤器,建立默认的 dispatcher 对象而且设置静态资源的包。
public void init(FilterConfig filterConfig) throws ServletException { try { this.filterConfig = filterConfig; // 初始化日志器 initLogging(); dispatcher = createDispatcher(filterConfig); dispatcher.init(); dispatcher.getContainer().inject(this); staticResourceLoader.setHostConfig(new FilterHostConfig(filterConfig)); } finally { ActionContext.setContext(null); } }
destory 方法:核心业务是调用 dispatcher.cleanup() 方法。cleanup 释放全部绑定到 dispatcher 实例的资源,包括销毁全部的拦截器实例,本方法在后面有源代码讨论。
public void destroy() { if (dispatcher == null) { log.warn("something is seriously wrong, Dispatcher is not initialized (null) "); } else { try { dispatcher.cleanup(); } finally { ActionContext.setContext(null); } } }
doFilter 方法:doFilter 方法的出口有 3 个分支。
首先过滤器尝试把 request 匹配到一个 Action mapping(action mapping 的解释见最后的总结)。如有匹配,执行 path1。不然执行 path2 或者 3。
path 1调用 dispatcher. serviceAction() 方法处理 Action 请求
若是找到了 mapping,Action 处理被委托给 dispatcher 的 serviceAction 方法。 若是 Action 处理失败了,doFilter 将会经过 dispatcher 建立一个错误页。
path 2处理静态资源
若是请求的是静态资源。资源被直接拷贝到 response 对象,同时设置对应的头信息。
path 3无处理直接经过过滤器,访问过滤器链的下个资源。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; ServletContext servletContext = getServletContext(); String timerKey = "FilterDispatcher_doFilter: "; try { //1 处理前的准备 //1.1 建立值栈对象,值栈包含 object stack 和 context map 两个部分。 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); //1.2 建立 actionContext。 ActionContext ctx = new ActionContext(stack.getContext()); ActionContext.setContext(ctx); UtilTimerStack.push(timerKey); //1.3 准备和包装 request request = prepareDispatcherAndWrapRequest(request, response); ActionMapping mapping; //2 根据请求路径查找 actionMapping try { mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); } catch (Exception ex) { log.error("error getting ActionMapping", ex); dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); return; } //3 当请求路径没有对应的 actionMapping,走第 2 和第 3 个出口 if (mapping == null) { String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); } else { chain.doFilter(request, response); } // 若是是第 2 和第 3 个出口 ,action 的处理到此结束。 return; } //3.1 路径 1,委托 dispatcher 的 serviceAction 进行处理 dispatcher.serviceAction(request, response, servletContext, mapping); } finally { try { //4 清除 ActionContext ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } }
对 doFilter() 方法的几点说明 :
valueStack 的创建是在 doFilter 的开始部分,在 Action 处理以前。即便访问静态资源 ValueStack 依然会创建,保存在 request 做用域。
ValueStack 在逻辑上包含 2 个部分:object stack 和 context map,object stack 包含 Action 与 Action 相关的对象。context map 包含各类映射关系。request,session,application,attr,parameters 都保存在 context map 里。
parameters: 请求参数
atrr: 依次搜索 page, request, session, 最后 application 做用域。
准备和包装 request,包含 2 步 :
准备请求,设置区域和字符编码
包装 request,若是包含文件上传,则网页表单设置为 multipart/form-data,返回 MultiPartRequestWrapper 类型。
若是普通表单提交,返回 StrutsRequestWrapper。这个类是 multipart/form-data 的父类。
ActionMapping 表明 struts.xml 文件中的一个 Action 配置,被传入到 serviceAction 中。注意 ActionMapping 不表明 Action 集合,只表明某个对应的 Action。
<action name="Pay" class=" "> <interceptor-ref name="tokenSession" / > <interceptor-ref name="basicStack" / > <result name="input">/jsp/Payment.jsp</result> <result>/jsp/Thanks.jsp</result> </action>
若是是一个 Action 请求,( 请求路径在 struts.xml 有对应的 Action 配置 ),则调用 dispatcher.serviceAction() 处理。
finally 语句块中调用 ActionContextCleanUp.cleanUp(req),以清除 ActionContext。
下边,咱们将讨论 dispatcher 类。
Dispatcher 作为实际派发器的工具类,委派大部分的处理任务。核心控制器持有一个本类实例,为全部的请求所共享。 本部分分析了两个重要方法。
serviceAction():加载 Action 类,调用 Action 类的方法,转向到响应结果。响应结果指代码清单 5 中 <result/> 标签所表明的对象。
cleanup():释放全部绑定到 dispatcher 实例的资源。
根据 action Mapping 加载 Action 类,调用对应的 Action 方法,转向相应结果。
首先,本方法根据给定参数,建立 Action context。接着,根据 Action 的名称和命名空间,建立 Action 代理。( 注意这代理模式中的代理角色 ) 而后,调用代理的 execute() 方法,输出相应结果。
若是 Action 或者 result 没有找到,将经过 sendError() 报 404 错误。
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap (request, response, mapping, context); //1 如下代码目的为获取 ValueStack,代理在调用的时候使用的是本值栈的副本 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(); } } //2 建立 ValueStack 的副本 if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); //3 这个是获取配置文件中 <action/> 配置的字符串,action 对象已经在核心控制器中建立 String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); // xwork 的配置信息 Configuration config = configurationManager.getConfiguration(); //4 动态建立 ActionProxy ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class). createActionProxy(namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); //5 调用代理 if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } //6 处理结束后,恢复值栈的代理调用前状态 if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { //7 若是 action 或者 result 没有找到,调用 sendError 报 404 错误 if(devMode) { LOG.error("Could not find action or result", e); } else { LOG.warn("Could not find action or result", e); } sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } finally { UtilTimerStack.pop(timerKey); } }
几点说明:
Valuestack 对象保存在 request 里,对应的 key 是 ServletActionContext.STRUTS_VALUESTACK_KEY。调用代理以前首先建立 Valuestack 副本,调用代理时使用副本,调用后使用原实例恢复。本处的值栈指 object stack。
Dispatcher 实例,建立一个 Action 代理对象。并把处理委托给代理对象的 execute 方法。
若是你不懂代理模式,那么在下一部分,我会简单介绍这种模式。但本处使用的是动态代理。动态代理,指代理类和代理对象都是动态生成的,不须要编写类的源代码。
createContextMap 的建立,建议去看一下。代码很简单。
释放全部绑定到 dispatcher 实例的资源
public void cleanup() { //1 销毁 ObjectFactory ObjectFactory objectFactory = getContainer().getInstance(ObjectFactory.class); if (objectFactory == null) { LOG.warn("Object Factory is null, something is seriously wrong, no clean up will be performed"); } if (objectFactory instanceof ObjectFactoryDestroyable) { try { ((ObjectFactoryDestroyable)objectFactory).destroy(); } catch(Exception e) { LOG.error(" exception occurred while destroying ObjectFactory ["+objectFactory+"]", e); } } //2 为本线程销毁 Dispatcher 实例 instance.set(null); //3 销毁 DispatcherListeners(Dispatcher 监听器 )。 if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherDestroyed(this); } } //4 调用每一个拦截器的 destroy() 方法,销毁每一个拦截器 Set<Interceptor> interceptors = new HashSet<Interceptor>(); Collection<Interceptor> packageConfigs = configurationManager. getConfiguration().getPackageConfigs().values(); for (PackageConfig packageConfig : packageConfigs) { for (Object config : packageConfig.getAllInterceptorConfigs().values()) { if (config instanceof InterceptorStackConfig) { for (InterceptorMapping interceptorMapping : ((InterceptorStackConfig) config).getInterceptors()) { interceptors.add(interceptorMapping.getInterceptor()); } } } } for (Interceptor interceptor : interceptors) { interceptor.destroy(); } //5 销毁 action context ActionContext.setContext(null); //6 销毁 configuration configurationManager.destroyConfiguration(); configurationManager = null; }
几点说明:
ObjectFactory 对象工厂,用来建立核心的框架对象,好比拦截器、Action、result 等等。本类处于 xwork 的包中。
DispatcherListeners 为监听器设计模式,仅仅在 Dispatcher 的 init 和 destory 中执行,也就是说仅仅在 Dispatcher 初始化和销毁的时候动做。
actionContext 是一个 context,Action 在其中执行。actionContext 从根本上讲是一个容器,action 执行所须要的对象,如会话,请求参数,区域信息,valueStack 等,包含在其中。
第 6 步销毁 xwork 配置对象。
代理模式有 3 个角色:
抽象主题 (abstract subject)
真实主题 (real subject)
代理主题 (proxy subject)
咱们以买笔记本电脑为例
抽象主题为抽象类或接口,定义了 request() 的行为,就是买电脑。
真实主题为买 hp 笔记本,要调用实现接口的 request() 方法,固然你找不到 hp 公司,你只能找到销售 hp 笔记本的电脑公司。
代理主题为销售 hp 笔记本的电脑公司。这家公司可能会说,今天买电脑都送一台数码相机,也可能跟你打折等等。总之在代理主题角色执行的时候,销售公司能够发生某些行为,发生的这些行为叫加强 advice,加强只能发生在代理角色。
代理模式的使用场景,加强是代理的目的。
public interface Subject { abstract public void request(); } class RealSubject implements Subject { public RealSubject() {} public void request() { System.out.println(" From real subject. "); } } // 代理角色 class ProxySubject implements Subject { private RealSubject realSubject; // 真实主题对象 public ProxySubject() {} public void preRequest() {} public void postRequest() {} public void request() { preRequest(); if (realSubject == null) { realSubject = new RealSubject(); } // 此处执行真实对象的 request 方法 realSubject.request(); postRequest(); } }
代理角色是切面,preRequest 为前置加强,postRequest 为后置加强。固然切面 aspect 的标准定义为两个要素:加强加切入。
你编写的 preRequest() 和 postRequest() 方法必定会参与到真实主题的的 request() 方法执行中。
假设你还不了解,我想请问,若是有个机会,一个很漂亮的妹妹的 MM 要你帮她买东西,你会不会本身贴点钱,或者说些话,让 MM 以为开心一些。若是是,你就是切面,你的额外的事情和钱就是切面上的加强。
动态代理中的 代理角色 = 切面 = 拦截器。请看下面的实现。
// 省略 Subject 接口和 RealSubject 类 // 调用处理器的类 class DynamicSubject implements InvocationHandler { private Object sub; public DynamicSubject() {} public DynamicSubject(Object obj) { sub = obj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(" before calling " + method); method.invoke(sub, args); System.out.println(" after calling " + method); return null; } } // 客户类 class Client { static public void main(String[] args) throws Throwable { RealSubject rs = new RealSubject(); // 真实主题 InvocationHandler ds = new DynamicSubject(rs); Class cls = rs.getClass(); // 生成代理对象 Subject subject = (Subject) Proxy.newProxyInstance( cls.getClassLoader(), cls.getInterfaces(), ds); subject.request(); } }
动态代理必须依赖于反射。动态代理,代理类和代理对象都是运行时生成的 (runtime),因此称为动态代理。InvocationHandler 实现类的原代码参与到代理角色的执行。通常在 Invoke 方法中实现加强。
好,在本部分总结的末尾,我再强调一遍概念:动态代理中的代理角色 = 切面 = 拦截器。
Dispatcher 类的 serviceAction() 方法中,执行处理的核心语句为 proxy.execute()。
ActionProxy 类,也就是代理角色,持有 ActionInvocation 的实例引用。ActionInvocation 表明着 Action 执行的状态,它持有着拦截器和 Action 实例的引用。ActionInvocation 经过反复调用 invoke() 方法,调用沿着拦截器链向下走。走完拦截器链后运行 Action 实例,最后运行 Result。
Result 每每对应网页 (jsp,freemarker,velocity),网页的结果被写到输出流里。客户端接收到请求的网页。
你看到 postRequest() 方法了吗,它在真实主题结束后运行。这也决定了,为何 Action 运行结束后,控制权还要按照拦截器链返回。
前面咱们提到一个概念,value Stack 包含两个部分。可是书上也说,不少时候或者是一般特指 Object Stack,用术语说就是 OGNL value stack。怎么理解 ?
|--application | |--session context map---| |--value stack(root) | |--request | |--parameters | |--attr (searches page, request,session, then application scopes)
说个人结论,而后再看原代码。
Struts2 框架,把 ActionContext 设置为 OGNL 上下文。ActionContext 持有 application,session,request,parameters 的引用。ActionContext 也持有 value stack 对象的引用 ( 注意这个时候 value stack 特指 Object stack)。
上述对象的引用,ActionContext 不直接持有,而是经过本身的属性 Map<String, Object> context 持有引用。处理 OGNL 表达式最顶层的对象是 Map<String, Object> context。
public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; /** * 值栈在 map 的 key,map 确定是 key-value 对结构了,别说你不知道。map 指本类最后一个属性 context。 */ public static final String VALUE_STACK = ValueStack.VALUE_STACK; /** * session 的 key,如下省略 */ public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session"; public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application"; public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters"; public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation"; //map 的定义 Map<String, Object> context; public ActionInvocation getActionInvocation() { return (ActionInvocation) get(ACTION_INVOCATION); } // 对做用域对象的引用 public Map<String, Object> getApplication() { return (Map<String, Object>) get(APPLICATION); } public void setSession(Map<String, Object> session) { put(SESSION, session); } public Map<String, Object> getSession() { return (Map<String, Object>) get(SESSION); } // 对 valueStack 的引用 public void setValueStack(ValueStack stack) { put(VALUE_STACK, stack); } public ValueStack getValueStack() { return (ValueStack) get(VALUE_STACK); } // 最关键的代码 public Object get(String key) { return context.get(key); } public void put(String key, Object value) { context.put(key, value); } }
那么 value stack 类是什么样子呢?值栈是一个数据结构的栈。全部的数据都保存在 root 对象中。
public interface ValueStack { public abstract CompoundRoot getRoot(); // 省略细节 public abstract Object peek(); public abstract Object pop(); public abstract void push(Object o); } public class CompoundRoot extends ArrayList { // 省略细节 }
你所编写的 Action 类实例,被放在 value stack 里。OGNL 访问 Action 实例的属性,能够省略 #。若是使用了 #, 表示所查找的对象不在 root 里,而在其余位置,好比 session。
在 Action 里若是访问 session ?最直接的方式是使用 ActionContext 获得。第二种方式是实现 SessionAware 接口。
我在总结以前仍是但愿你们看一下官方的流程图(图 2)。
若是你能够彻底看懂上面的图,那你能够省略这一部分。可是好在本部分都是精华的,并且很少。
FilterDispatcher 接到请求,查找对应的 Action Mapping,调用 Dispatcher 类的 serviceAction() 方法。
Dispatcher 类的 serviceAction() 方法中建立而且调用 ActionProxy。
ActionProxy,持有 ActionInvocation 的实例引用。ActionInvocation 表明着 Action 执行的状态,它持有着拦截器和 Action 实例的引用。ActionInvocation 经过反复调用 invoke() 方法,调用沿着拦截器链向下走。
走完拦截器链后运行 Action 实例,最后运行 Result。
你们注意到拦截器链了吗?它才是 Struts2.0 的核心所在。
最后一个问题,一般咱们编写 Struts2 只有一个过滤器 FilterDispatcher,为何这边是三个过滤器 ?SiteMesh 能够对你编写的页面进行装饰,以美化界面,固然笔者的界面刚好属于通常般,刚脱离丑的那种类型。若是 SiteMesh 要访问值栈 value stack,原来清除值栈的工做由 FilterDispatcher 完成。org.apache.struts2.dispatcher.ActionContextCleanUp 告诉 FilterDispatcher 不要清除值栈,由本身来清除。