博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
struts2中值栈valueStack和大map ContextMap的关系:
阅读量:381 次
发布时间:2019-03-05

本文共 13094 字,大约阅读时间需要 43 分钟。

简述:ActionContext类中持有ContextMap对象,由源码可知创建ActionContext对象时,必须传入一个map对象,这个对象就是OGNL表达式取值的的上下文环境ContextMap,而ContextMap中又存储了valueStack对象以及request、servletContext(application)及RequestMap、sessionMap、applicationMap、parameters、attr(各种小Map)以及当前的动作类action对象。而valueStack中又持有ContextMap对象以及CompoundRoot对象,前者就是OGNL表达式取值的的上下文环境,叫做map栈或者大Map,后者List的子类对象,叫做对象栈或者root栈。通常我们说的从valueStack中取值,便是指从CompoundRoot对象中取值。

valueStack:

ValueStack对象概述

ValueStackStruts的一个接口,字面意义为值栈,OgnlValueStack是ValueStack的实现类,客户端发起一个请求struts2架构会创建一个action实例同时创建一个OgnlValueStack值栈实例,OgnlValueStack贯穿整个 Action 的生命周期。

它是ContextMap中的一部分,里面的结构是一个List,是我们可以快速访问数据一个容器。它的封装是由struts2框架完成的。

通常情况下我们是从页面上获取数据。它实现了栈的特性(先进后出)。

ValueStack的内部结构

OnglValueStack 中包含了一个CompoundRoot的对象,该对象继承了ArrayList并且提供了只能操作集合第一个元素的方法,所以我们说它实现了栈的特性。同时,它里面定义了一个ContextMap的引用,也就是说,我们有值栈对象,也可以通过值栈来获取ContextMap

大Map(ContextMap):

小map(session、requestservletContext对应的map等等):

valuestack栈顶元素分析:

首先从struts2的核心过滤器StrutsPrepareAndExecuteFilter入手:

init方法中的config参数指定了要加载的配置文件,所以在核心过滤器的初始化阶段,便读取struts.xml配置文件,加载struts2框架。源代码:

public void init(FilterConfig filterConfig) throws ServletException {        InitOperations init = new InitOperations();        Dispatcher dispatcher = null;        try {            FilterHostConfig config = new FilterHostConfig(filterConfig);            init.initLogging(config);            dispatcher = init.initDispatcher(config);            init.initStaticContentLoader(config, dispatcher);            prepare = new PrepareOperations(dispatcher);            execute = new ExecuteOperations(dispatcher);            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);            postInit(dispatcher, filterConfig);        } finally {            if (dispatcher != null) {                dispatcher.cleanUpAfterInit();            }            init.cleanup();        }    }

doFilter()方法中,实例化ActionContext对象和ContextMap对象,代码如下:

 
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        try {            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {                chain.doFilter(request, response);            } else {                prepare.setEncodingAndLocale(request, response);                prepare.createActionContext(request, response);                prepare.assignDispatcherToThread();                request = prepare.wrapRequest(request);                ActionMapping mapping = prepare.findActionMapping(request, response, true);                if (mapping == null) {                    boolean handled = execute.executeStaticResourceRequest(request, response);                    if (!handled) {                        chain.doFilter(request, response);                    }                } else {                    execute.executeAction(request, response, mapping);                }            }        } finally {            prepare.cleanupRequest(request);        }    }

其中,

prepare.createActionContext(request, response);
createActionContext的源码如下:
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {        ActionContext ctx;        Integer counter = 1;        Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);        if (oldCounter != null) {            counter = oldCounter + 1;        }                ActionContext oldContext = ActionContext.getContext();//1        if (oldContext != null) {            // detected existing context, so we are probably in a forward            ctx = new ActionContext(new HashMap
(oldContext.getContextMap()));//2 } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();//3 stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//4 ctx = new ActionContext(stack.getContext());//5 } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; }

红色代码表示创建actionContext的过程,且在创建actionContext时,传入了一个map对象,该map对象就是contextMap。并且每次请求都会创建一个新的actionContext对象,所以actionContext对象时非单例的。

红色代码第4行(stack.getContext()的返回值便是ContextMap对象):

stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//4

其中dispatcher.createContextMap(request, response, null)的源码如下:

public Map
createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) { // request map wrapping the http request objects Map requestMap = new RequestMap(request); // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately Map params = new HashMap(request.getParameterMap()); // session map wrapping the http session Map session = new SessionMap(request); // application map wrapping the ServletContext Map application = new ApplicationMap(servletContext); Map
extraContext = createContextMap(requestMap, params, session, application, request, response); if (mapping != null) { extraContext.put(ServletActionContext.ACTION_MAPPING, mapping); } return extraContext; }

public HashMap
createContextMap(Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response) { HashMap
extraContext = new HashMap
(); 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(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);//1 extraContext.put("attr", attrMap);//2 return extraContext; }

上述代码便是整个ContextMap产生的过程,ContextMap中持有的对象,仅有红色代码中的request、response、servletContext对象时servlet原装的对象,其余都是经过封装的map对象(map子类持有request、session等等对象)。以上map中存放的信息,便是<s:debug/>标签在浏览器中查看到的信息。

注意1/2两行红色代码,attr为key对应的map拥有ContextMap中除了valueStack之外的所有信息,所以网页debug标签可以看到存放在域中的数据,在attr处也存有一份,便是这个道理(存放在域对象中的数据在ContextMap中其实有3份,一份在原装的域对象中,一份在封装域对象的Map中,一份在attr中)。

以下为某<s:debug/>标签在浏览器中的部分截图:

图1:

图2:

图3:

图4:

图5:

图6:

以下三张图片也是debug标签中的部分截图,截图中搜索了application对象中的全部数据,显示application中的数据再ContextMap中有3份,原装application一份,对应的map中有一份,在attr中还有一份,具体如图:

图1-1:

图1-2:

图1-3:

ActionContext:

在核心过滤器的的doFilter方法中,执行了 ActionContext.setContext(actionContext);该方法源码如下:

static ThreadLocal
actionContext = new ThreadLocal
();...public static void setContext(ActionContext context) { actionContext.set(context); }
红色的actionContext对象是一个线程局部变量,它的作用的将actionContext对象绑定到当前线程。前面已经知道doFilter方法中每次请求都会创建一个新的actionContext对象,即每个线程绑定的对象都是全新的,即actionContext对象线程私有且唯一。actionContext.set(context)的源码如下:
public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

线程局部变量以当前线程为key,以actionContext为value,存入当前线程对象中的一个ThreadLocalMap中,从而实现了与当前线程绑定。

ActionContext.getContext();方法如下:

public static ActionContext getContext() {        return actionContext.get();    }
actionContext.get()源码如下:
public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        return setInitialValue();    }
即:从当前线程中取出actionContext对象。

以下为向大map和小map存取值的部分代码示例:

Struts.xml文件:

/index.jsp
/test01.jsp

存值的Action动作类的方法:

/**	 * map部分的存值操作	 * @return	 */		/*	 * ognl上下文对象,就是ContextMap,ContextMap中的valueStack取值不用加#	 */	public String demo02() {		ActionContext context = ActionContext.getContext();		//由context向ContextMap中存入数据		context.put("contextMap", "我由Context存入ContextMap,因为context.put方法底层为contextMap.put");//直接存入大map		context.put("contextMap01", "***********我由Context存入ContextMap,因为context.put方法底层为contextMap.put");//直接存入大map		Map
applicationMap = context.getApplication(); applicationMap.put("applicationMap", "我是由applicationMap放入的");//存到了大map中的小map里面 ServletContext servletContext = ServletActionContext.getServletContext(); servletContext.setAttribute("applicationAttr", "我是由原装的application对象放入上下文域的");//存到了大map中的小map里面 return "demo02"; }

取值的jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"	pageEncoding="UTF-8"%><%@ taglib uri="/struts-tags" prefix="s" %>
Insert title here <%--2、使用OGNL表达式获取Map中的数据 需要借助s:property获取map的数据:都是根据Key获取value,所以我们要提供的是key。如何表示key:使用#,后面的内容就表示key写法: #key--%>获取大Map中的数据(因为是直接存到大Map中的,所以s:property value="#contextMap"便可取出):

<%--3、使用OGNL表达式获取大Map中指定小Map的数据也需要使用s:property标签写法就是 #key.key --%>

存入到application或者applicationMap中的数据,最终都会互相存入,且复制一份到attr中


获取application对应的小map中的数据(因为是存到大map中的小map中所以,需要先在大map中找到小map,再从小map中取值,所以:s:property value="#application.applicationAttr"):


从原装application中获取存入的数据:

从attr中获取存入application中的数据(同理先找到小mapattr,再找数据):

红色代码没取到值,以下是浏览器显示结果(原因:原装的application对象存储的数据是框架自己用的,我们无法操作,而对应的map才是给开发者用的,我们只能从对应的map中取值,无法从原装的对象的中取值):

以下是<s:debug/>标签部分图解:

向session和sessionMap存值示例:

action动作类中的方法:

public String demo03() {		//获取ActionContext:从当前线程上获取它		ActionContext ac = ActionContext.getContext();		System.out.println(ac);				//往大map中存入值		ac.put("contextMap", "往大map存入值:小飞飞在这里");				//往小map中存入值:application		/**		 * key1:     com.opensymphony.xwork2.ActionContext.application      框架用的长的		 * key2:  application                                            我们用的短的【关注】		 * key3:  attr		                                                                                                                            全域查找		 * 		 */		ac.getApplication().put("applicationMap", "小map:applicationMap中的值:小鸭鸭在这里");				/**		 * 往小map中存入值		 */		ac.getSession().put("sessionMap", "小map:sessionMap : 我是由sessionMap中存入的");				/**		 * 往会话域中存入值:HttpSession		 * 我们操作小map,就相当于操作域对象:HttpSession		 * 		 * 往session和sessionMap中存入值,最终都会同时存到sessionMap和原装session中		 */		ServletActionContext.getRequest().getSession().setAttribute("sessionAttr", "我是由原装的session对象存入的");										return "demo03";	}

jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><%-- 1、导入Struts2的标签库 --%><%@ taglib uri="/struts-tags" prefix="s"%>
大map的取值

获取大map中的值

<%--取值:通过OGNL表达式取值 获取大map中的值,OGNL表达式前面必须加入一个 “#” --%> 获取大map中的值:
获取大map中的session(value="#session"):

获取框架用的原装的session对象(获取不到,因为是框架自己用的):


<%--
--%>

获取小map的值

<%--Struts2为了让我们快速看到大map的结构,给我们提供了一个debug标签页面有个超链接,点击一下,就会展开大map的结构 --%>

浏览器截图:

大map中同时出现了session和sessionMap:

sessionMap:

原装session:

模型驱动对象:

/**	 * 模型驱动对象,封装请求参数	 */	private Customer customer = new Customer();	@Override	public Customer getModel() {//反射后的对象名是model。		return customer;//customer对象在值栈中的名称其实是model,因为反射调用的get方法默认get后面的名字对对象的名字	}private List
custSources; private List
custLevels; public List
getCustSources() { return custSources; } public List
getCustLevels() { return custLevels; }/** * 修改客户页面跳转 * @return */ private Customer customer02; public Customer getCustomer02() { return customer02; } @Action(value="editCustomerUI",results= { @Result(name="editCustomerUI",location="/jsp/customer/edit.jsp") }) public String editCustomerUI() { //customer02在action中 customer02 = customerService.findCustomerById(customer.getCustId()); customer = customer02;//customer对应model对象 //压栈customer,栈顶确实customer对象,但动作类中一模一样的是model对象。 ServletActionContext.getContext().getValueStack().push(customer); //查询用户信息来源 custSources = baseDicService.findAllCustSources(); //查询用户级别 custLevels = baseDicService.findAllCustLevels(); return "editCustomerUI"; }

valueStack截图:

你可能感兴趣的文章