Struts2 ActionContext 中的数据详解
actioncontext
actioncontext是action的上下文,struts2自动在其中保存了一些在action执行过程中所需的对象,比如session, parameters, locale等。struts2会根据每个执行http请求的线程来创建对应的actioncontext,即一个线程有一个唯一的actioncontext。因此,使用者可以使用静态方法actioncontext.getcontext()来获取当前线程的actioncontext,也正是由于这个原因,使用者不用去操心让action是线程安全的。
无论如何,actioncontext都是用来存放数据的。struts2本身会在其中放入不少数据,而使用者也可以放入自己想要的数据。actioncontext本身的数据结构是映射结构,即一个map,用key来映射value。所以使用者完全可以像使用map一样来使用它,或者直接使用action.getcontextmap()方法来对map进行操作。
struts2本身在其中放入的数据有actioninvocation、application(即servletcontext)、conversionerrors、locale、action的name、request的参数、http的session以及值栈等。完整的列表请参考它的javadoc(本文附录有对它包含内容的讨论)。
由于actioncontext的线程唯一和静态方法就能获得的特性,使得在非action类中可以直接获得它,而不需要等待action传入或注入。需要注意的是,它仅在由于request而创建的线程中有效(因为request时才创建对应的actioncontext),而在服务器启动的线程中(比如fliter的init方法)无效。由于在非action类中访问其的方便性,actioncontext也可以用来在非action类中向jsp传递数据(因为jsp也能很方便的访问它)。
valuestack与actioncontext的联系和区别:
相同点:它们都是在一次http请求的范围内使用的,即它们的生命周期都是一次请求。
不同点:值栈是栈的结构,actioncontext是映射(map)的结构。
联系:valuestack.getcontext()方法得到的map其实就是actioncontext的map。查看struts2的源代码可知(struts2.3.1.2的org.apache.struts2.dispatcher.ng.prepareoperations的第79行,createactioncontext方法),在创建actioncontext时,就是把valuestack.getcontext()作为actioncontext的构造函数的参数。所以,valuestack和actioncontext本质上可以互相获得。
注意:在一些文档中,会出现把对象存入“stack's context”的字样,其实就是把值存入了actioncontext。所以在阅读这些文档时,要看清楚,到底是放入了栈结构(即值栈),还是映射结构(值栈的context,即actioncontext)。
如何获得actioncontext:
在自定义的拦截器中:使用actioninvocation.getinvocationcontext()或者使用actioncontext.getcontext()。
在action类中:让拦截器注入或者使用actioncontext.getcontext()。
在非action类中:让action类传递参数、使用注入机制注入或者使用actioncontext.getcontext()。注意:只有运行在request线程中的代码才能调用actioncontext.getcontext(),否则返回的是null。
在jsp中:一般不需要获得actioncontext本身。
如何向actioncontext中存入值:
在拦截器、action类、非action类等java类中:使用actioncontext.put(object key, object value)方法。
在jsp中:标签<s:set value="..."/>默认将值存入actioncontext中(当然,<s:set>标签还可以把值存到其他地方)。另外,许多标签都有var属性(以前用的是id属性,现在id属性已被弃用),这个属性能向actioncontext存入值,key为var属性的值,value为标签的value属性的值。(有些文档写的是向valuestack的context存入值,其实是一样的)
如何从actioncontext中读取值:
在拦截器、action类、非action类等java类中:使用actioncontext.get(object key)方法。
在jsp中:使用#开头的ognl表达式,比如<s:property value="#name"/>会调用actioncontext.get("name")方法。注意:如果某标签的属性默认不作为ognl表达式解析,则需要使用%{}把表达式括起来,于是就会出现类似“%{#name}的表达式”。(“#”的更多用途参见这里)
总之,在jsp中使用actioncontext一方面是由于它是映射结构,另一方面是能读取action的一些配置。当你需要为许多action提供通用的值的话,可以让每个action都提供getxxx()方法,但更好的方法是在拦截器或jsp模板中把这些通用的值存放到actioncontext中(因为拦截器或jsp模板往往通用于多个action)。
一些例子:
java代码
// 本类将演示拦截器中对actioncontext的操作 publicclass myinterceptor extends abstractinterceptor { public string intercept(actioninvocation invocation) throws exception { // 获得actioncontext actioncontext actioncontext = invocation.getinvocationcontext(); // 存入值 person person = new person(); actioncontext.put("person", person); // 获取值 object value = actioncontext.get("person"); // 获取httpservletrequest httpservletrequest request = (httpservletrequest) actioncontext.get(strutsstatics.http_request); // 获取request的map,即httpservletrequest.getattribute(...)和httpservletrequest.setattribute(...)所操作的值 map requestmap = (map) actioncontext.get("request"); // 其他代码 // ...... return invocation.invoke(); } }
java代码
// 本类将演示在action中对actioncontext进行操作 publicclass myaction extends actionsupport { @override public string execute() throws exception { // 获得值栈 actioncontext actioncontext = actioncontext.getcontext(); // 存入值 person person = new person();// 这是之前例子中定义的类 actioncontext.put("person", person); // 获取值 object object = actioncontext.get("person"); // 其他代码 // ...... return success; } }
html代码
<!doctype html> <html> <head> <metahttp-equiv="content-type"content="text/html; charset=utf-8"> <title>jsp page</title> </head> <body> <!-- 本jsp将演示在jsp中对actioncontext的使用 --> <!-- 本jsp为myaction对应的jsp --> <!-- 由于action中已经向actioncontext存入了key为"person"的值,所以可以使用“#person”来获取它,如下 --> <s:propertyvalue="#person"/> <!-- 获得person的name属性,如下 --> <s:propertyvalue="#person.name"/> <!-- 获得struts2在actioncontext中存入的值,比如request的map,如下 --> <s:propertyvalue="#request"/> <!-- 获得struts2在actioncontext中存入的值,比如session的map,如下 --> <s:propertyvalue="#session"/> <!-- 获得struts2在actioncontext中存入的值,request请求传递的get参数或post参数的map,如下 --> <s:propertyvalue="#parameters"/> <!-- 以下演示在jsp中把值存入actioncontext中 --> <!-- 存入一个字符串"myname",key为"mykey",如下 --> <s:setvalue="%{'myname'}"var="mykey"/> <!-- 使用s:bean标签来创建一个对象,并把它存入actioncontext中,key为myobject,如下 --> <s:beanname="com.example.person"var="myobject"/> <!-- 之后就可以用“#”来读取它们,如下 --> <s:propertyvalue="#mykey"/> <s:propertyvalue="#myobject"/> </body> </html>
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传值。
一些例子:
// 本类将演示拦截器中对httpservletrequest和request的map的操作 publicclass myinterceptor extends abstractinterceptor { public string intercept(actioninvocation invocation) throws exception { // 获得actioncontext actioncontext actioncontext = invocation.getinvocationcontext(); // 获得httpservletrequest httpservletrequest httpservletrequest=(httpservletrequest)actioncontext.get(strutsstatics.http_request); // 获得request的map map requestmap = (map) actioncontext.get("request"); // 创建一个类作为实例 person person = new person(); // 以下两行的语句作用相同 httpservletrequest.setattribute("person", person); requestmap.put("person", person); // 其他代码 // ...... return invocation.invoke(); } }
// 本类将演示在action中对httpservletrequest和request的map进行操作(静态方法获得actioncontext) publicclass myaction extends actionsupport { @override public string execute() throws exception { // 获得actioncontext actioncontext actioncontext = actioncontext.getcontext(); // 获得httpservletrequest httpservletrequest httpservletrequest=(httpservletrequest)actioncontext.get(strutsstatics.http_request); // 获得request的map map requestmap = (map) actioncontext.get("request"); // 创建一个类作为实例 person person = new person(); // 以下两行的语句作用相同 httpservletrequest.setattribute("person", person); requestmap.put("person", person); // 其他代码 // ...... return success; } }
// 本类将演示在action中使用servletrequestaware获得httpservletrequest(注意:要使用servletconfiginterceptor拦截器) publicclass myaction extends actionsupport implements servletrequestaware { private httpservletrequest request; //此方法是接口servletrequestaware的方法 publicvoid setservletrequest(httpservletrequest request) { this.request = request; } @override public string execute() throws exception { // httpservletrequest已在该类的字段中准备好,可直接使用 // ...... return success; } }
// 本类将演示在action中使用servletrequestaware获得request的map(注意:要使用servletconfiginterceptor拦截器) publicclass myaction extends actionsupport implements requestaware { map<string, object> request; // 该方法是接口requestaware的方法 publicvoid setrequest(map<string, object> request) { this.request = request; } @override public string execute() throws exception { // request的map已在该类的字段中准备好,可直接使用 // ...... return success; } }
<!doctype html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>jsp page</title> </head> <body> <!-- 本jsp将演示在jsp中对request的map的使用 --> <!-- 本jsp为myaction对应的jsp --> <!-- request的map是struts2自动在actioncontext中存入的值(key为request),所以使用“#”来访问actioncontext,从中读取request --> <s:property value="#request"/> <!-- 以下两行均是访问request的map中key为“name”的值 --> <s:property value="#request.name"/> <s:property value="#request['name']"/> </body> </html>
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。