struts2中值栈valueStack和大map ContextMap的关系:

简述: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<String, Object>(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<String,Object> 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<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response);

        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) {
        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(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> actionContext = new ThreadLocal<ActionContext>();
.
.
.
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文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">

<struts>

    
    <!-- 开发者模式 -->
    <constant name="struts.devMode" value="true" />

    <package name="p1" extends="struts-default">
    	<action name="*" class="cn.yew.web.action.Demo01Action" method="{1}">
    		<result name="{1}" type="dispatcher">/index.jsp</result>
    		<result name="demo02">/test01.jsp</result>
    	</action>
    </package>

</struts>

存值的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<String, Object> 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" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<%--2、使用OGNL表达式获取Map中的数据 需要借助s:property
获取map的数据:都是根据Key获取value,所以我们要提供的是key。
如何表示key:使用#,后面的内容就表示key
写法:
	#key
--%>
获取大Map中的数据(因为是直接存到大Map中的,所以s:property value="#contextMap"便可取出):<br>
<s:property value="#contextMap"/>
<hr/>
<%--3、使用OGNL表达式获取大Map中指定小Map的数据
也需要使用s:property标签
写法就是
	#key.key
 --%>
 <h3>存入到application或者applicationMap中的数据,最终都会互相存入,且复制一份到attr中</h3>
 <hr>
获取application对应的小map中的数据(因为是存到大map中的小map中所以,需要先在大map中找到小map,再从小map中取值,所以:s:property value="#application.applicationAttr"):<br>
<s:property value="%{#application.applicationMap}"/><br/>
<hr>
<s:property value="#application.applicationAttr"/>
<hr>
从原装application中获取存入的数据:<br>
<s:property value="#com.opensymphony.xwork2.ActionContext.application.applicationAttr"/><br>
<s:property value="#com.opensymphony.xwork2.ActionContext.application.applicationMap"/><br>
<hr>
从attr中获取存入application中的数据(同理先找到小mapattr,再找数据):<br>
<s:property value="#attr.applicationAttr"/><br>
<s:property value="#attr.applicationMap"/>
<hr>
<s:debug/>
</body>
</html>

红色代码没取到值,以下是浏览器显示结果(原因:原装的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"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>大map的取值</title>
</head>
<body>
<H3>获取大map中的值</H3>

<%--
取值:通过OGNL表达式取值
	获取大map中的值,OGNL表达式前面必须加入一个 “#”
 --%>
 获取大map中的值:<br>
<s:property value="#contextMap"/><br>
获取大map中的session(value="#session"):<br>
<s:property value="#session"/><br>
<hr>
<h3>获取框架用的原装的session对象(获取不到,因为是框架自己用的):</h3>
<s:property value="#com.opensymphony.xwork2.ActionContext.session"/><br>
<hr>
<%-- <s:property value="#application"/><br> --%>


<h3>获取小map的值</h3>
<s:property value="#session.sessionMap"/><br>
<s:property value="#session.sessionAttr"/><br>
<s:property value="#application.applicationMap"/><br>
<%--
Struts2为了让我们快速看到大map的结构,给我们提供了一个debug标签
页面有个超链接,点击一下,就会展开大map的结构
 --%>
<s:debug/>

</body>
</html>

浏览器截图:


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

sessionMap:


原装session:




模型驱动对象:

/**
	 * 模型驱动对象,封装请求参数
	 */
	private Customer customer = new Customer();
	@Override
	public Customer getModel() {//反射后的对象名是model。
		return customer;//customer对象在值栈中的名称其实是model,因为反射调用的get方法默认get后面的名字对对象的名字
	}


private List<BaseDict> custSources;
	private List<BaseDict> custLevels;
	
	public List<BaseDict> getCustSources() {
		return custSources;
	}
	public List<BaseDict> 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截图:

















  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值