当前位置: 移动技术网 > IT编程>开发语言>Jsp > struts2数据同步原理

struts2数据同步原理

2018年03月10日  | 移动技术网IT编程  | 我要评论
用过struts2的人都知道,struts2有个很大的特点就是可以不再面向Servlet API编程,从Action的方法签名就可以看出,其execut方法不接收任何参数,返回值也仅仅是String.从而实现与Servlet API的解耦,语法层面上脱离了Web容器。

当要在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数据同步的原理就到这了,如有错误之处,尽请指正。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网