struts2数据同步原理

   用过struts2的人都知道,struts2有个很大的特点就是可以不再面向Servlet API编程,从Action的方法签名就可以看出,其execute方法不接收任何参数,返回值也仅仅是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,下面是这两个方法的源码:

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;
    }

 

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方法,下面是该方法源码:

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中传递数据是这样做的:

ActionContext ctx = ActionContext.getContext(); //获取ActionContext对象
ctx.put(key, value); //往request中存储数据
ctx.getSession().put(key, value); //往session中存储数据
ctx.getApplication().put(key, value); //往application中存储数据

如果我们紧接着在上面代码写这样的代码:

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方法源码:

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方法源码:

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方法的源码:

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方法中的这句代码:

attribute = stack.findValue(s);

ValueStack是一个接口,sturts2使用的其实现类是OgnlValueStack,下面是OgnlValueStack.findValues方法源码

public Object findValue(String expr) {
         return findValue(expr, false);
     }

这里又调用了另一个重载的findValue方法,进去看看

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)方法,我们进放该方法:

private Object findInContext(String name) {
        return getContext().get(name);
    }

这里getContext()返回的就是ognl.OgnlContext对象,再调用了其get方法,再进入get方法:

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":

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值