欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

struts2数据同步原理

程序员文章站 2022-09-02 19:34:31
用过struts2的人都知道,struts2有个很大的特点就是可以不再面向Servlet API编程,从Action的方法签名就可以看出,其execut方法不接收任何参数,返回值也...
用过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数据同步的原理就到这了,如有错误之处,尽请指正。