• Struts2值栈


    一、前言

      很多事儿啊,就是“成也萧何败也萧何”,细想一些事儿心中有感,当然,感慨和本文毛关系都没有~想起之前有篇Struts2中值栈的博客还未完工,就着心中的波澜,狂咽一把~

    二、正文

      博文基于:struts-core-2.5.2.jar,ognl-3.1.10.jar

      和值栈的邂逅还是大学学习Struts2的时候,老师当时在台上津津有味,我却在底下晕头转向~很长时间都没有去理清这个值栈到底是什么,实现原理是什么,偶然的际遇,让我又遇到了它,所以我决定以我现有的知识储备再次去追根溯源~

      那从概念上,我们要怎么去理解值栈呢?你可以将它理解为一个容器(就像装水的杯子,只不过它里面“装”的不是水,而是java对象),这边“装”的意思是“引用”了其他类或者对象,我想这样说应该能理解吧~其实呢,作为开发人员,再精准的文字描述也很难让自己理解到技术的精华,所以看源码是进阶的必经之路~源码的精妙有些时候会让人如痴如醉,编程,就应该是一门艺术~

      在Struts2中,值栈和ActionContext是同步创建的,这个创建过程可以在Struts2的核心过滤器:org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter中,doFilter方法中查看,源码如下:

     1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
     2 
     3         HttpServletRequest request = (HttpServletRequest) req;
     4         HttpServletResponse response = (HttpServletResponse) res;
     5 
     6         try {
     7             String uri = RequestUtils.getUri(request);
     8             if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
     9                 LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri);
    10                 chain.doFilter(request, response);
    11             } else {
    12                 LOG.trace("Checking if {} is a static resource", uri);
    13                 boolean handled = execute.executeStaticResourceRequest(request, response);
    14                 if (!handled) {
    15                     LOG.trace("Assuming uri {} as a normal action", uri);
    16                     prepare.setEncodingAndLocale(request, response);
    17                     prepare.createActionContext(request, response);
    18                     prepare.assignDispatcherToThread();
    19                     request = prepare.wrapRequest(request);
    20                     ActionMapping mapping = prepare.findActionMapping(request, response, true);
    21                     if (mapping == null) {
    22                         LOG.trace("Cannot find mapping for {}, passing to other filters", uri);
    23                         chain.doFilter(request, response);
    24                     } else {
    25                         LOG.trace("Found mapping {} for {}", mapping, uri);
    26                         execute.executeAction(request, response, mapping);
    27                     }
    28                 }
    29             }
    30         } finally {
    31             prepare.cleanupRequest(request);
    32         }
    33     }

      在上面的代码中,我们可以看到17行“prepare.createActionContext(request, response);”就是创建ActionContext的代码,其具体实现的源码如下:

     1 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
     2         ActionContext ctx;
     3         Integer counter = 1;
     4         Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
     5         if (oldCounter != null) {
     6             counter = oldCounter + 1;
     7         }
     8         
     9         ActionContext oldContext = ActionContext.getContext();
    10         if (oldContext != null) {
    11             // detected existing context, so we are probably in a forward
    12             ctx = new ActionContext(new HashMap<>(oldContext.getContextMap()));
    13         } else {
    14             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
    15             stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
    16             ctx = new ActionContext(stack.getContext());
    17         }
    18         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
    19         ActionContext.setContext(ctx);
    20         return ctx;
    21     }

       进入14行中的createValueStack方法内部可以知道值栈的实现者为:OgnlValueStack

    1    //class OgnlValueStackFactory
    2    public ValueStack createValueStack() {
    3         ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess);
    4         container.inject(stack);
    5         stack.getContext().put(ActionContext.CONTAINER, container);
    6         return stack;
    7     }

      值栈OgnlValueStack中包含两个重要的成员变量,CompoundRoot root(这其实是一个ArrayList)和context.(我们通常称为contextMap):

    1 CompoundRoot root;
    2 transient Map<String, Object> context;
    3 
    4 //其中CompoundRoot的声明如下
    5 public class CompoundRoot extends CopyOnWriteArrayList<Object> 

      再进入OgnlValueStack的构造函数和setRoot方法:

     1    protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticAccess) {
     2         setRoot(xworkConverter, accessor, new CompoundRoot(vs.getRoot()), allowStaticAccess);
     3     }
     4 
     5 
     6    protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot,
     7                            boolean allowStaticMethodAccess) {
     8         this.root = compoundRoot;
     9         this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
    10         this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess);
    11         context.put(VALUE_STACK, this);
    12         Ognl.setClassResolver(context, accessor);
    13         ((OgnlContext) context).setTraceEvaluations(false);
    14         ((OgnlContext) context).setKeepLastEvaluation(false);
    15     }

      从第11行我们可以看出,OgnlValueStack中的context有包含了对自身的引用,我们回头再来看看第10行Ognl.createDefaultContext方法内部的实现:

     1 //class Ognl.java
     2 public static Map createDefaultContext(Object root, ClassResolver classResolver,TypeConverter converter, MemberAccess memberAccess)
     3 {
     4     return addDefaultContext(root, classResolver, converter, memberAccess, new OgnlContext());
     5 }
     6 
     7 public static Map addDefaultContext(Object root, ClassResolver classResolver,TypeConverter converter, MemberAccess memberAccess, Map context)
     8 {
     9         OgnlContext result;
    10 
    11         if (!(context instanceof OgnlContext)) {
    12             result = new OgnlContext();
    13             result.setValues(context);
    14         } else {
    15             result = (OgnlContext) context;
    16         }
    17         if (classResolver != null) {
    18             result.setClassResolver(classResolver);
    19         }
    20         if (converter != null) {
    21             result.setTypeConverter(converter);
    22         }
    23         if (memberAccess != null) {
    24             result.setMemberAccess(memberAccess);
    25         }
    26 
    27         result.setRoot(root);
    28         return result;
    29 }

      从方法addDefaultContext可以得知,OnglValueStack中的context是OnglContext对象。第27行可以看出,这个context还包含了对OgnlValueStack中“CompoundRoot root”的引用。

      接下来,我们回到PrepareOperation.java类中的“createActionContext”方法(StrutsPrepareAndExecuteFilter.java中doFilter中调用这个方法,正文最开始已经贴出了源码):

     1 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
     2         ActionContext ctx;
     3         Integer counter = 1;
     4         Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
     5         if (oldCounter != null) {
     6             counter = oldCounter + 1;
     7         }
     8         
     9         ActionContext oldContext = ActionContext.getContext();
    10         if (oldContext != null) {
    11             // detected existing context, so we are probably in a forward
    12             ctx = new ActionContext(new HashMap<>(oldContext.getContextMap()));
    13         } else {
    14             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
    15             stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
    16             ctx = new ActionContext(stack.getContext());
    17         }
    18         request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
    19         ActionContext.setContext(ctx);
    20         return ctx;
    21     }

      从之前的源码分析,我们知道,14行的ValueStack存储的是OgnlValueStack对象,这个对象的getContext方法获取成员变量context(Map类型,具体实现类为OgnlContext)。我们再来看看15行dispatcher.createContextMap的源码:

     1 // class Dispacher.java
     2 
     3 public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
     4             ActionMapping mapping) {
     5 
     6         // request map wrapping the http request objects
     7         Map requestMap = new RequestMap(request);
     8 
     9         // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
    10         Map params = new HashMap(request.getParameterMap());
    11 
    12         // session map wrapping the http session
    13         Map session = new SessionMap(request);
    14 
    15         // application map wrapping the ServletContext
    16         Map application = new ApplicationMap(servletContext);
    17 
    18         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response);
    19 
    20         if (mapping != null) {
    21             extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
    22         }
    23         return extraContext;
    24     }

      看着上面Map类型的requestMap、params等变量,是不是觉得很熟悉?我们在使用ActionContext的时候是不是经常有如下用法,这些值就在这边设置的:

    1         ActionContext.getContext().getParameters();
    2         ActionContext.getContext().get("request");
    3         ActionContext.getContext().getSession();        

      等等,是不是又有读者要问了,我值栈ValueStack中的context怎么又和ActionContext扯上关系了?有迷惑的读者,请往前两段代码看下createActionContext的第16行:

      再看下类ActionContext的声明和构造函数:

     1    .........................................................................................
     2 public class ActionContext implements Serializable {
     3 
     4     static ThreadLocal<ActionContext> actionContext = new ThreadLocal<>();
     5     private Map<String, Object> context;
     6 
     7     public ActionContext(Map<String, Object> context) {
     8         this.context = context;
     9     }
    10     public static void setContext(ActionContext context) {
    11         actionContext.set(context);
    12     }
    13     public static ActionContext getContext() {
    14         return actionContext.get();
    15     }
    16    ........................................................................
    17 }

      OgnlValueStack将成员变量context传递给ActionContext对象的context成员变量,之前在OgnlValueStack设置的值便都能在ActionContext的对象中获取~当然这边还有一个非常关键的一步:

      这一步将ActionContext实例变量设置到ThreadLocal变量中,这个就使得ActionContext实例变量可以在同一个线程中的不同类间传递,对于ThreadLocal使用不太清晰的读者,请参考我之前的博文:ThreadLocal 验明正身

      至此,已经从源码的角度将Struts2中的“值栈”说清楚了~下面附张图,可以更好的帮助读者去理解本文内容~(别人画的,本文参考链接中有贴出原链接 )

     

    (本图用来说明OnglValueStack中context和ActionContext中context的关系)

    总结:

      当初会去深究Struts2中的值栈,起因是在使用Ognl表达式时,获取“值栈”中某些值,有些表达式要加“#”有些不要,然后就有了去深究的冲动~

      “值栈”由两部分组成:

      1)ObjectStack (保存为root属性,类型CompoundRoot) ----- ArrayList
    
      2)ContextMap(保存为context属性, 类型  ) ------ Map

          Struts2 会把下面这些映射压入 ContextMap 中:

      parameters: 该 Map 中包含当前请求的请求参数
      request: 该 Map 中包含当前 request 对象中的所有属性
      session: 该 Map 中包含当前 session 对象中的所有属性
      application:该 Map 中包含当前 application  对象中的所有属性
      attr: 该 Map 按如下顺序来检索某个属性: request, session, application

      注:CompoucdRoot继承了ArrayList,实际上就是一个集合,用于存储元素的数据,OgnlContext实现了Map, 其中持有CompoucdRoot对象的引用,其key为_root

      在JSP页面内,通过 <s:property>等标签去访问值栈的数据,访问root中数据,不需要“#”,访问 Context中数据必须以“#”开始。
      当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action ,然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
    Action对象会被保存root 对象栈中,Action所有成员变量属性,都位于root中 ,访问root中数据不需要“#”。

      再附一张图,图中“ValueStack(值栈,根对象)”指的是OgnlValueStack类中的CompoundRoot root成员变量:

    三、链接

    http://blog.csdn.net/javaliuzhiyue/article/details/9357337

    http://www.cnblogs.com/x_wukong/p/3887737.html

    http://blog.csdn.net/elvis12345678/article/details/7909936

    http://www.cnblogs.com/hlhdidi/p/6185836.html

    四、联系本人

      为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园

  • 相关阅读:
    编写好代码的10条戒律
    [Project] 基开放云平台
    [Project] HUSTOJ随笔
    编码规范:大家都应该做的事情
    ural 1167. Bicolored Horses 夜
    1709. PenguinAvia 夜
    hdu 1011 Starship Troopers 夜
    hdu 2571 命运 夜
    hdu 1561 The more, The Better 夜
    hdu 1598 find the most comfortable road 夜
  • 原文地址:https://www.cnblogs.com/xdouby/p/6536088.html
Copyright © 2020-2023  润新知