转自:http://www.ibm.com/developerworks/cn/java/j-lo-struct2/index.html
我们从看官方的流程图开始。当本篇文章结束的时候,我们会再一遍来看它。
- 初始的请求通过一条标准的过滤器链,到达 servlet 容器 ( 比如 tomcat 容器,WebSphere 容器 )。
- 过滤器链包括可选的 ActionContextCleanUp 过滤器,用于系统整合技术,如 SiteMesh 插件。
- 接着调用 FilterDispatcher,FilterDispatcher 查找 ActionMapper,以确定这个请求是否需要调用某个 Action。
- 如果 ActionMapper 确定需要调用某个 Action,FilterDispatcher 将控制权交给 ActionProxy。
- ActionProxy 依照框架的配置文件(struts.xml),找到需要调用的 Action 类。
- ActionProxy 创建一个 ActionInvocation 的实例。ActionInvocation 先调用相关的拦截器 (Action 调用之前的部分),最后调用 Action。
- 一旦 Action 调用返回结果,ActionInvocation 根据 struts.xml 配置文件,查找对应的转发路径。返回结果通常是(但不总是,也可能是另外的一个 Action 链)JSP 技术或者 FreeMarker 的模版技术的网页呈现。Struts2 的标签和其他视图层组件,帮助呈现我们所需要的显示结果。在此,我想说清楚一些,最终的显示结果一定是 HTML 标签。标签库技术和其他视图层技术只是为了动态生成 HTML 标签。
- 接着按照相反次序执行拦截器链 ( 执行 Action 调用之后的部分 )。最后,响应通过滤器链返回(过滤器技术执行流程与拦截器一样,都是先执行前面部分,后执行后面部)。如果过滤器链中存在 ActionContextCleanUp,FilterDispatcher 不会清理线程局部的 ActionContext。如果不存在 ActionContextCleanUp 过滤器,FilterDispatcher 会清除所有线程局部变量。
备注:拦截和过滤器的执行顺序可能一些人理解不了,我以生活中的范例说明。我去上海的 IBM 实验室出差,火车沿途停靠蚌埠,南京,最终达到上海。办完事情后回来,沿途的停靠站是南京、蚌埠。有没有注意到火车停靠站的顺序相反了。好,转到我们遇到的技术问题,上海的业务相当于 Action 执行,是调用的真正目标。蚌埠和南京是两个分别的过滤器。即使我两次路过南京,只是一个过滤器的调用先执行一半后执行一半罢了。
核心控制器 org.apache.struts2.dispatcher.FilterDispatcher
传统的 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 作用域。
图 3. 值栈
- 准备和包装 request,包含 2 步 :
准备请求,设置区域和字符编码
包装 request,如果包含文件上传,则网页表单设置为 multipart/form-data,返回 MultiPartRequestWrapper 类型。
如果普通表单提交,返回 StrutsRequestWrapper。这个类是 multipart/form-data 的父类。 - ActionMapping 代表 struts.xml 文件中的一个 Action 配置,被传入到 serviceAction 中。注意 ActionMapping 不代表 Action 集合,只代表某个对应的 Action。
清单 6. 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 类。
org.apache.struts2.dispatcher.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。怎么理解 ?
OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。
|--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 不要清除值栈,由自己来清除。



