struts2数据同步原理
当要在Web层即控制器向视图层传递数据时,传统做法都是存储在HttpServletRequest、HttpServletSession、ServletContext对象中。
而在struts2中使用其提供的API就可以操作request,session,application这些用于向页面传递数据的对象,这里说的request,session,application是不同于上面所讲的,它们是Map对象,所以才实现与Servlet API的解耦。当然还有parameters对象,还有一个stuts2扩展的attr对象,下面就详细讲解这其中的原理。
在讲解之前须要知道ActionContext是如何创建而来的,所以先把这个讲明白:
struts2的核心分发器Dispatcher中有两重载的方法:createContextMap,下面是这两个方法的源码:
[java]
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);//将request封装成一个RequestMap对象
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
Map params = new HashMap(request.getParameterMap());//将请求参数放在HashMap中
// session map wrapping the http session
Map session = new SessionMap(request);//将session封装在一个SessionMap中(通过request.getSession可获取session)
// application map wrapping the ServletContext
Map application = new ApplicationMap(context);//把ServeltContext封装在成一个ApplicationMap对象
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
[java]
public HashMap<String,Object> createContextMap(Map requestMap,
Map parameterMap,
Map sessionMap,
Map applicationMap,
HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext) {
HashMap<String,Object> extraContext = new HashMap<String,Object>();
extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
extraContext.put(ActionContext.SESSION, sessionMap);
extraContext.put(ActionContext.APPLICATION, applicationMap);
Locale locale;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
} else {
locale = request.getLocale();
}
extraContext.put(ActionContext.LOCALE, locale);
//extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));
extraContext.put(StrutsStatics.HTTP_REQUEST, request);
extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
// helpers to get access to request/session/application scope
extraContext.put("request", requestMap);
extraContext.put("session", sessionMap);
extraContext.put("application", applicationMap);
extraContext.put("parameters", parameterMap);
AttributeMap attrMap = new AttributeMap(extraContext);
extraContext.put("attr", attrMap);
return extraContext;
}
首先要说明的是createContextMap方法返回的Map中的数据就是要放到ActionContext中的数据。
第一个createContextMap所做的工作看注释应该都知道了,但有一个细节不知道大家注意到没有,为什么parameters是存放在一个HashMap中,而不是在一个ParametersMap中,当然ParametersMap这个类是不存在的,sturts2开发者没有为请求参数开发一个用于封装请求参数的类,而是直接存放在HashMap中。但是像request,session,application都有对应的封装Map类。这是因为对于请求参数来说在请求到来的时候就已经确定了,不会再变化,而request,session,application这些对象到了Action或者Interceptor中我们会向其添加数据,封装后利于在Action中与Servlet API解耦。
第一个createContextMap调用了第二个createContextMap方法,把Servlet原生的与封装好的request,session,application都传了进去,第二个createContextMap方法所做的工作也很简单就是把Servlet原生的与封装好的对象都放在了一个HashMap(extraContext)中,然后返回,注意这个大的HashMap中的所有数据都会放到ActionContext中。
还有一个要先讲的就是在struts2中,所使用的request是struts2经过包装的StrutsRequestWrapper类,继承自javax.servlet.http.HttpServletRequestWrapper,并且覆盖了getAttribute方法,下面是该方法源码:
[java]
public Object getAttribute(String s) {
if (s != null && s.startsWith("javax.servlet")) {
// don't bother with the standard javax.servlet attributes, we can short-circuit this
// see WW-953 and the forums post linked in that issue for more info
return super.getAttribute(s);//先去父类中找,即在HttpServletRequest中查找
}
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(s);//这里同上
if (ctx != null) {
if (attribute == null) {//如果没有找到
boolean alreadyIn = false;
Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
if (b != null) {
alreadyIn = b.booleanValue();
}
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && s.indexOf("#") == -1) {
try {
// If not found, then try the ValueStack
ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(s);//如果在HttpServletRequest没有找到则在ValueStack查找
}
} finally {
ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
}
}
}
}
return attribute;
}
正如注释中所说的如果在HttpServletRequest中没有找到则在ValueStack中查找。这一点是非重要的,到后面的讲解中就能体会得到。
做完上面的准备工作可以进行正题了:
在Action中我们向View中传递数据是这样做的:
[java]
ActionContext ctx = ActionContext.getContext(); //获取ActionContext对象
ctx.put(key, value); //往request中存储数据
ctx.getSession().put(key, value); //往session中存储数据
ctx.getApplication().put(key, value); //往application中存储数据
如果我们紧接着在上面代码写这样的代码:
[java]
ServletActionContext.getRequest().getAttribute(key); //在HttpServletRequest中获取上面存储的数据
ServletActionContext.getRequest().getSession().getAttribute(key); //在HttpSession中获取上面存储的数据
ServletActionContext.getServletContext().getAttribute(key); //在ServletContext中获取上面存储的数据
你会“奇怪”地发现立即就能获取到上面存储的数据,数据很快地在Map中与Servlet对象中同步了,这是为什么呢?
因为存储在request中的原理更复杂所以把它放到后面,先讲session与application。
session数据同步原理:
ctx.getSession()看方法签名返回值是一个Map,其真正的实现类就是前面所讲的SessionMap,其实现了java.util.Map接口在SessionMap创建的时候struts2把HttpServletRequest对象传递了进去,这样当然就可以操作HttpSession对象了。下面是SessionMap的put方法源码:
[java]
public V put(K key, V value) {
synchronized (this) {
if (session == null) {
session = request.getSession(true);
}
}
synchronized (session) {
entries = null;
session.setAttribute(key.toString(), value);
return get(key);
}
}
大家可以看到该方法内部就是把我们要存储的值放到了HttpSession中。
下面是SessionMap的get方法源码:
[java]
public V get(Object key) {
if (session == null) {
return null;
}
synchronized (session) {
return (V) session.getAttribute(key.toString());
}
}
获取的时候理所当然就在HttpSesion中获取了,这里在HttpSession中地行存放与读取数据中加了synchronized关键字,这是因为对于有些浏览器可能存在两个进程操作的是同一个HttpSession的情况。
application数据同步原理:
这与session数据同步原理基本上是一样的,ctx.getApplication()返回的真实对象是一个ApplicationMap对象,在该对象创建的时候传入了ServletContext,当然其内部就是操作的ServletContext对象,这个就不说源码了,大家看一下应该都能明白了。
最后讲request数据同步原理:
当我们调用ctx.put方法时其实调用的是ActionContext内部维护的一个名为context对象的put方法,这个context对象的类型为ognl.OgnlContext,即OGNL表达式上下文。下面是OgnlContext的put方法的源码:
[java]
public Object put(Object key, Object value)
{
Object result;
if (RESERVED_KEYS.containsKey(key)) {
//这里省略了很多判断代码...
} else {
result = _values.put(key, value);
}
return result;
}
当我们存储普通数据的时候都是存储在了一个叫_values的对象中,翻一下源码就知道_values其实就是一个OgnlContext内部维护的HashMap。现在我们知道了,当我们调用ActionContext.getContext().put(key,value)方法的时候其实就是存放在一个OgnlContext内部维护的HashMap中。
再看我们调用HttpServletRequest.getAttribute(key)是如何获取到通过调用ActionContext.getContext().put(key,value)存储的值的。这里就要用到上面的结论了,我们获取的HttpServletRequest对象其实是经过struts2包装后的StrutsRequestWrapper对象,其覆盖了getAtrribute方法,当在HttpServletRequest中找不到指定key的值的时候就会去ValueStack中查找。
通过上面我们可以知道当我们调用ActionContext.getContext().put(key,value)方法存储数据时根本就没有存储在HttpServletRequest对象中,所以在HttpServletRequest中是根据找不到数据的,所以会在ValueStack中查找。
即会执行StrutsRequestWrapper中的getAtrribute方法中的这句代码:
[java]
attribute = stack.findValue(s);
ValueStack是一个接口,sturts2使用的其实现类是OgnlValueStack,下面是OgnlValueStack.findValues方法源码
[java]
public Object findValue(String expr) {
return findValue(expr, false);
}
这里又调用了另一个重载的findValue方法,进去看看
[java]
public Object findValue(String expr, boolean throwExceptionOnFailure) {
try {
if (expr == null) {
return null;
}
if ((overrides != null) && overrides.containsKey(expr)) {
expr = (String) overrides.get(expr);
}
if (defaultType != null) {
return findValue(expr, defaultType);
}
Object value = ognlUtil.getValue(expr, context, root); //在ValueStack的root对象中查找,当然是找不到的
if (value != null) {
return value;
} else {
checkForInvalidProperties(expr, throwExceptionOnFailure, throwExceptionOnFailure);
return findInContext(expr); //在OgnlContext中查找,看,这里又绕回来了,我们存的时候就是存在OgnlContext中
}
} catch (OgnlException e) {
checkForInvalidProperties(expr, throwExceptionOnFailure, throwExceptionOnFailure);
return findInContext(expr);
} catch (Exception e) {
logLookupFailure(expr, e);
if (throwExceptionOnFailure)
throw new XWorkException(e);
return findInContext(expr);
} finally {
ReflectionContextState.clear(context);
}
}
我们存的时候并不是存在ValueStack的root对象中,而是存在OgnlContext中,这里的查找顺序则好,先去root对象中查找,如果找不到再去OgnlContext中查找,即findInContext(expr)方法,我们进放该方法:
[java]
private Object findInContext(String name) {
return getContext().get(name);
}
这里getContext()返回的就是ognl.OgnlContext对象,再调用了其get方法,再进入get方法:
[java]
public Object get(Object key)
{
Object result;
if (RESERVED_KEYS.containsKey(key)) {
//这里省略了很多判断代码... 翻开源码就会发现get与put方法那些判断条件都是一样的,我们存储的普通数据不会进入这里
} else {
result = _values.get(key);
}
return result;
}
到这里相信大家都明白了,最终调用的OgnlContext.get(Object key)方法中,内部就是在其内部维护的_values对象中查找,而我们存储的时候就是存放在这个_values对象中,然后返回,当然也就找到了我们存储的数据。
还记得上面在第一个createContextMap方法中,struts2把HttpServletRequest对象封装成了一个RequestMap对象,并存放在ActionContext中,key为"request":
[java]
extraContext.put("request", requestMap);
但是ActionContext中并没有直接提供一个API(getRequest())去获取RequestMap对象,其原因我想就是因为通过上面的机制就已经实现request数据同步功能。当然你也可以调用ActionContext.getContext().get("request")去获取RequestMap对象,然后在RequestMap中获取存储的数据,因为RequestMap中查找时就是在HttpServletRequest中查找,当然这个HttpServletRequest是struts2包装过的StrutsRequestWrapper对象,查找时又进行上述流程,最终查找到我们存储的数据。
在页面中获取数据的时候我们是通过OGNL表达式,这个就不用多说了,当然就是在OgnlContext与ValuesStack中查找的。
通过上面所讲的大家想想:通过EL表达是不是也能访问到request,session,application范围中存储的数据呢?
至此呢,struts2数据同步的原理就到这了,如有错误之处,尽请指正。
上一篇: SQL学习笔记之运算符、函数与子查询详解
下一篇: 安卓开发(二)人脸识别相册FaceMap