首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

在Struts2中应用ValueStack、ActionContext、ServletContext、request、session等

2012-07-04 
在Struts2中使用ValueStack、ActionContext、ServletContext、request、session等? ? 笔者不知道该用哪个词来

在Struts2中使用ValueStack、ActionContext、ServletContext、request、session等

? ? 笔者不知道该用哪个词来形容ValueStack、ActionContext等可以在Struts2中用来存放数据的类。这些类使用的范围不同,得到的方法也不同,下面就来一一介绍。

? ? 声明:本文参考Struts2版本为2.3.1.2,内容仅供参考,限于笔者水平有限,难免有所疏漏,望您能友善指出。本文发表于ITEYE,谢绝转载。

1. ValueStack

? ? ValueStack在中文版的《Struts2深入浅出》一书中译作“值栈”。其本身数据结构是一个栈,使用者可以把一些对象(又称作bean)存入值栈中,然后使用动态的表达式来读取bean的属性,或者对bean进行一些其他操作。由于值栈中可能有多个bean,值栈会按bean出栈的顺序依次尝试使用动态的表达式来读取值,直到成功读取值为止。在Struts2中,默认的值栈实现是OgnlValueStack,即默认使用Ognl这个动态表达式语言来读取值。

? ? 在Struts2执行一次请求的过程中,Struts2会把当前的Action对象自动放入值栈。这样,在渲染JSP时,JSP里的代码使用<s:property value="..."/>之类标签中的Ognl表达式会直接作用于Action对象,从而方便的读取Action的属性。

?? ? 如何得到值栈:在自定义的拦截器中,使用ActionInvocation.getStack()方法( ActionInvocation 是拦截器的方法参数)。在Action类中,让拦截器注入ValueStack或者使用ActionContext.getContext().getValueStack()来值栈(ActionContext.getContext()为静态方法)。注意:ActionContext分配context的方式是基于线程的,如果使用这种方法,请确保它不会出错。在JSP中,直接使用标签即可获得值栈里的数据,而一般不用获取值栈本身。? ? 如何将对象存入值栈:Struts2自动存入Action:之前已经提到,Struts2在执行一次请求的过程中会把当前的Action对象自动存入值栈中。ModelDrivenInterceptor会存入Action的model属性:如果你使用了Struts2提供的ModelDrivenInterceptor,则它会把Action对象的getModel()方法得到的对象存入值栈中。此时,值栈最底层为Action类,其次为这个model。在自定义的拦截器中存入值栈:得到值栈对象后调用ValueStack.put(Object object)方法。在Action类中存入值栈:得到值栈对象后调用ValueStack.put(Object object)方法。在JSP中存入值栈:标签<s:push value="..."></s:push>是专门用来在JSP中把指定的value放入值栈的,但value被放入值栈的时间仅在s:push标签内,即程序运行到</s:push>标签处会把value从值栈中移出。另外,还有一些标签比如<s:iterator/>由于其功能的需要也会把一些对象放到值栈中。? ? 让值栈执行表达式来获得值:在自定义的拦截器中,获得值栈后,使用ValueStack.findValue(...)等方法。在Action类中,获得值栈后,使用ValueStack.findVlaue(...)等方法。在JSP中,一些标签的属性是直接在值栈上执行Ognl表达式的,比如<s:property/>的value属性。如果标签的属性不是直接执行Ognl表达式的,则需要使用“%{}”将表达式括起来,这样Struts2就会以Ognl表达式来执行了。至于到底哪些标签是直接执行Ognl而哪些不是,请参考完整的官方文档。? ? 在JSP中跳过栈顶元素直接访问第二层:在JSP中,使用[0]、[1]等表达式来指定从栈的第几层开始执行表达式。[0]表示从栈顶开始,[1]表示从栈的第二层开始。比如表达式“name”等价于“[0].name”。参见此处。? ? 在JSP中访问值栈对象本身(而不是它们的属性)在表示式中使用top关键字来访问对象本身。比如,表达式“name”等价于“top.name”,表达式“[0].top”等价于“top”,表达式“[1].top.name”等价于“[1].name”。

? ? 总之,值栈主要目的是为了让JSP内能方便的访问Action的属性。


? ? 一些例子:
?3. HttpServletRequest类或request的Map

? ? Struts2中提供了两种对request的操作:一种是Web服务器提供的HttpServletRequest类,这和传统Java Web项目中的操作request的方式相同;另一种是一个“request的Map”,即封装了HttpServletRequest的attributes的映射类,操作该Map相当于操作HttpServletRequest的attributes。之所以提供了Map的操作方式,一是方便操作,二是能方便使用Ognl在JSP标签中读取request。无论如何,这两个request是互通的。至于request的生命周期等概念,与其他的Java Web项目没有区别,本文不再详述。

?

? ? 使用HttpServletRequest类还是request的Map虽然两者是互通的,但就读取request的attributes而言,使用request的Map要方便许多,并且不会暴露不必要的接口。当然,HttpServletRequest有一些request的Map没有的方法,使用这些方法时当然还是要用前者。? ? 使用request的Map还是ActionContext:两者都是Map,两者的生命周期都是一个请求。传统的Java Web项目中,往往是通过request的attributes来向JSP传递值的:先在Servlet里setAttribute(),然后在JSP里getAttribute()。当然在Struts2的项目中,你仍然可以使用这个方法,然而抛弃了Struts2提供的传递功能是得不偿失的。虽然笔者没有找到官方文档说一定要用ActionContext替换request的Map,也没有发现程序中有能获得ActionContext却获得不了request的Map的地方,但在Struts2框架下,操作ActionContext要比操作request的Map更加方便。因此,笔者建议:尽量使用ActionContext而不是request的Map来传递值。request的Map有时候会包含其他框架设置的值,比如Spring框架。获取这些值的时候就需要用request的Map了,因为ActionContext里没有。通过ActionContext可以获得HttpServletRequest类:“HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST);”。通过ActionContext也可以获得request的Map:“Map requestMap = (Map) actionContext.get("request");”。因此,在JSP标签中,使用表达式“#request”就可以获得request的Map的数据。? ? 如何获得HttpServletRequest:如果已经有ActionContext,则使用“actionContext.get(StrutsStatics.HTTP_REQUEST)”来获得HttpServletRequest。在自定义的拦截器中,先获得ActionContext,再通过ActionContext来获得。在Action中,先获得ActionContext,再通过ActionContext来获得。或者让Action实现ServletRequestAware接口,并使用ServletConfigInterceptor拦截器,这样这个拦截器就会注入HttpServletRequest。在JSP中,一般不需要获得HttpServletRequest。? ? 如何获得request的Map:如果已经有ActionContext,则使用“actionContext.get("request")”来获得。在自定义的拦截器中,先获得ActionContext,再通过ActionContext来获得。在Action中,先获得ActionContext,再通过ActionContext来获得。或者让Action实现RequestAware接口,并使用ServletConfigInterceptor拦截器,这样这个拦截器就会注入Map request。在JSP中,用“#request”来获得request的Map,用“#request.key”或者“#request['key']”来读取Map中的值。

? ? 总之,request仍然符合Java Web网站的一般规律。不过笔者建议使用者应尽量避免用request传值。

?

? ? 一些例子:

?

?

?

3. Parameters,即GET请求或POST请求的参数

? ? Parameters为GET或POST等请求时浏览器向服务器传递而来的参数。在传统的Java Web项目中,使用HttpServletRequest.getParameter()等方法来获取参数,并且可以直接使用HttpServletRequest.getParameterMap()来获得一个封装了参数的Map。而在Struts2中,Struts2直接把上述Map存放到了ActionContext中,key为“parameters”。另外,ActionContext还直接提供了ActionContext.getParameters()方法来获得这个Map。因此,在Struts2的各个部件中操作parameters的方法和操作request的Map的方法十分相似,本段不再详述。

?

4. HttpServletSession类和session的Map

? ? 传统Java Web项目中的session是我们都熟悉的,我们用它来记录一个用户的会话状态。Struts2把HttpServletSession封装到了一个Map中,即“session的Map”,这类似对request的处理。然而为了节省系统资源,我们在不需要session的时候不会创建session。可能正是因为这个缘故,Struts2中没有把HttpServletSession放入ActionContext中,如果你的程序需要使用HttpServletSession,应该先获得HttpServletRequest,然后使用getSession()或getSession(boolean b)来获得它,同时决定是否需要创建session。对于session的Map,Struts2仍然把它放入了ActionContext中(key为"session"),但是不要担心,这个Map的机制使得只有put新值时才会创建session。总之,Struts2中对HttpServletSession的操作要先通过HttpServletRequest来获得它,而对session的Map的操作与对request的Map的操作如出一辙,本段不再详述。

?

5. ServletContext和application的Map

? ? 传统的Java Web项目中,ServletContext用来存放全局变量,每个Java虚拟机每个Web项目只有一个ServletContext。这个ServletContext是由Web服务器创建的,来保证它的唯一性。ServletContext有一些方法能操作它的attributes,这些操作方法和操作一个Map类似。于是,Struts2又来封装了:它把ServletContext的attributes封装到了一个Map中,即“application的Map”,并且也放入的ActionContext中(key为application),因此,对application的Map的操作就如果对request的Map操作,本段不再详述。

? ? 至于对ServletContext的操作,与HttpServletRequest的操作类似:Struts2将ServletContext放到了 ActionContext中,并且ServletConfigInterceptor提供了对ServletContext的注入接口ServletContextAware。因此,本段不再详述。

? ? 注意:在Ognl表达式中使用“#application”可以得到application的Map,而不是ServletContext。然而在JSP嵌入的Java代码中(比如“<% application.getAttribute(""); %>”),application为ServletContext,而不是Map。

?

? ? 用一张表格来总结:

?

?

变量从ActionContext中获得生命周期用Ongl来读取值使用ServletConfigInterceptor来注入ActionContext类静态方法ActionContext. getContext()一次Http请求使用“#”加上key,如“#name”无法注入ValueStack类ActionContext. getValueStack()一次Http请求直接填写来访问栈中对象的方法,或者使用top来直接获得栈中对象无法注入HttpServletRequest类ActionContext. get( StrutsStatics. HTTP_REQUEST)一次Http请求无方便的方法实现ServletRequestAware接口request的MapActionContext. get("request")一次Http请求使用“#request”再加上key,如“#request.name”或者“#request['name']”实现RequestAware接口parameters的MapActionContext. get( "parameters")一次Http请求使用“#parameters”再加上key,如“#parameters.name”或者“#parameters['name']”实现ParameterAware接口HttpServletSession类无(需通过HttpServletRequest来获得)一次Http Session会话无方便的方法无法注入session的MapActionContext. get("session")每次请求创建,但在一次Http Session会话中数据都是一样的使用“#session”再加上key,如“#session.name”或者“#session['name']”实现SessionAware接口ServletContext类ActionContext. get( StrutsStatics. SERVLET_CONTEXT)网站项目启动后一直存在且唯一无方便的方法使用ServletContextAware接口application的MapActionContext.get( "application")每次请求时创建,但其中的数据是网站项目启动后一直存在且共享使用“#application”再加上key,如“#application.name”或者“#application['name']”使用ApplicationAware接口

?

附录1 ActionContext中到底有哪些数据

?

keykey的声明处value的类型value.toString()

com. opensymphony. xwork2. dispatcher.

HttpServletRequest

?StrutsStatics. HTTP_REQUESTorg. apache. struts2. dispatcher. StrutsRequestWrapperorg. apache. struts2. dispatcher. StrutsRequestWrapper @10984e0application?无org. apache. struts2. dispatcher. ApplicationMap略com. opensymphony. xwork2. ActionContext. locale?ActionContext. LOCALEjava. util. Localezh_CNcom. opensymphony. xwork2. dispatcher. HttpServletResponse?StrutsStatics. HTTP_RESPONSEorg. apache. catalina. connector. ResponseFacadeorg. apache. catalina. connector. ResponseFacade @14ecfe8

xwork. NullHandler.

createNullObjects

Booleanfalsecom. opensymphony. xwork2. ActionContext. name?ActionContext. ACTION_NAMEStringindex

com.opensymphony. xwork2.ActionContext.

conversionErrors

?ActionContext.?

CONVERSION_ERRORS

java. util. HashMap{}com. opensymphony. xwork2. ActionContext. application?ActionContext. APPLICATIONorg. apache. struts2. dispatcher. ApplicationMap略attr?无org. apache. struts2. util. AttributeMaporg. apache. struts2. util. AttributeMap @133a2a8com. opensymphony. xwork2. ActionContext. container?ActionContext. CONTAINERcom. opensymphony. xwork2. inject. ContainerImplcom. opensymphony. xwork2. inject. ContainerImpl @fc02c8com. opensymphony. xwork2. dispatcher. ServletContext?StrutsStatics. SERVLET_CONTEXTorg. apache. catalina. core. ApplicationContextFacadeorg. apache. catalina. core. ApplicationContextFacade @11ad78ccom. opensymphony. xwork2. ActionContext. session?ActionContext. SESSIONorg.apache.struts2. dispatcher.SessionMap{}

com.opensymphony. xwork2.ActionContext.

actionInvocation

?ActionContext. ACTION_INVOCATIONcom. opensymphony. xwork2. DefaultActionInvocationcom. opensymphony. xwork2. DefaultActionInvocation @13d4497xwork. MethodAccessor. denyMethodExecution?笔者很懒,没有找Booleanfalsereport. conversion. errors?笔者很懒,没有找Booleanfalsesession?无org. apache. struts2. dispatcher. SessionMap{}com. opensymphony. xwork2. util. ValueStack. ValueStack?ValueStack.VALUE_STACKcom. opensymphony. xwork2. ognl. OgnlValueStackcom. opensymphony. xwork2. ognl. OgnlValueStack @16237fdrequest?无org. apache. struts2. dispatcher. RequestMap略action?笔者很懒,没有找com. example. MyAction略struts. actionMapping?笔者很懒,没有找org. apache. struts2. dispatcher. mapper. ActionMappingorg. apache. struts2. dispatcher. mapper. ActionMapping @892cc5parameters?无java. util. HashMap{}com. opensymphony. xwork2. ActionContext. parameters?ActionContext.PARAMETERSjava. util. TreeMap{}

?

注意:该表格为了排版在某些地方加了空格。

?

可以看出,有些相同的对象被以不同的key多次设置到ActionContext中。如果想看看创建ActionContext的源代码,请看org.apache.struts2.dispatcher.Dispatcher的serviceAction方法和两个createContextMap方法。


附录2 Struts2标签中value属性直接对ActionContext访问的问题

? ? 经试验并查看相关源代码后发现,在使用<s:property value="..."/>时,该标签的执行类会先根据value中表达式到值栈中执行表达式来查找值。如果在值栈中找到值,就返回该值;如果没有找到,则把该表达式作为ActionContext的key,到ActionContext中去找值。比如<s:property value="request"/>也会得到ActionContext中的request,等价于<s:property value="#request"/>。但是,由于标签的执行类会认为该值时String类型的,并且会直接进行类型转换。于是,如果直接使用<s:property value="request"/>的话其实会让页面抛出异常:Request类型不能转换成String类型。所以,只能用如果不带#的话只能成功读取ActionContext中String类型的值。这种机制使得某些时候栈顶的属性可以覆盖ActionContext中的key,或许你正需要它。然而,鉴于这种机制的不确定性,笔者建议访问ActionContext中的数据一定要带上“#”,可以免去一些麻烦。

? ? 关于这种转型异常,笔者认为是Struts2的bug,源代码如下,当“value = getValue(expr, asType);”时,是考虑了asType的,但从context中读取时“value = findInContext(expr);”,就没有考虑asType,并且没有在其他地方看到类型检查操作:

?

?

// 本代码截取Struts2.3.1.2版本com.opensymphony.xwork2.ognl.OgnlValueStack类的第340行-352行    private Object tryFindValue(String expr, Class asType) throws OgnlException {        Object value = null;        try {            expr = lookupForOverrides(expr);            value = getValue(expr, asType);            if (value == null) {                value = findInContext(expr);            }        } finally {            context.remove(THROW_EXCEPTION_ON_FAILURE);        }        return value;    }
?

?

?

热点排行