Struts2 笔记
Struts2是在WebWork2基础发展而来的。和Struts1一样,Struts2也属于MVC框架。尽管二者在名字上的差别不是很大,但是在代码编写风格上几乎是不一样的。
Struts2主要有以下优点:
- 在软件设计上Struts2没有像Struts1那样更ServletAPI有着紧密的耦合,Struts2的应用可以不依赖于ServletAPI和StrutsAPI。Struts2的这种设计属于无侵入式设计,而Struts1却属于侵入式设计。
- Struts2提供了拦截器,利用拦截器可以进行AOP编程,实现如权限拦截等功能。
- Struts2提供了类型转换器,可以把特殊的请求参数转换成需要的类型。在Struts1中,如果要实现同样的功能,就必须向Struts1的底层实现BeanUtil注册类型转换器才行。
- Struts2提供支持多种表现层技术。如:JSP、freeMarker、Velocity等。
- Struts2的输入校验可以对指定方法进行校验,解决了Struts1长久之痛。
- 提供了全局范围、包范围和Action范围的国际化资源文件管理实现。
1. 环境搭建:
搭建Struts2环境,需要以下几个步骤:
- 找到开发Struts2应用需要使用到的jar文件,在发行包的lib目录中(不同版本需要的最小jar包是不同的,参见不同版本的文档 2.1.7)
struts2-core.jar Struts2框架的核心类库
xwork-2.jar xwork类库,Struts2在其上构建
ognl.jar 对象图导航语言(Object Graph Navigation Language),Struts2框架通过其读取对象的属性
freemarker.jar Struts2UI标签的模板使用freeMarker编写
commons-logging.jar ASF出品的日志包,Struts2框架使用这个日志包来支持Log4J和JDK1.4+的日志记录
commons-fileupload.jar 文件上传组件,2.1.6版本后必须加入此文件
commons-io.jar 文件上传依赖的包
- 编写Struts2的配置文件
Struts2默认的配置文件为struts.xml,该文件需要存放在WEB-INF/classes下,该文件的配置模板如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN" "http://struts.apache.org/dtds/struts-2.1.7.dtd"> <struts> </struts>
- 在web.xml中加入Struts2MVC框架启动配置
在Struts1.x中,Struts框架是通过Servlet启动的。在Struts2,Struts框架是通过Filter启动的。在web.xml中的配置如下
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
2. 第一个Struts2应用--HelloWorld
(1)编写struts.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN" "http://struts.apache.org/dtds/struts-2.1.7.dtd"> <struts> <!-- package:方便管理动作元素 name:必须有。包的名称,配置文件中必须保证唯一 namespace:该包的名称空间,一般是以“/”开头 extends:继承的父包的名称(在struts2-core.jar中有一个struts-default.xml) abstract:是否是抽象包。没有任何action元素就是抽象包 action:代表一个请求动作 name:同包中必须唯一。动作的名称 class:负责处理的JavaBean的类全名 method:JavaBean中的对应处理方法。动作方法,特点是:public String 方法名(){} result:结果类型 name:动作方法返回的字符串 主体内容:View的具体地址 --> <package name="ztq" namespace="/test" extends="struts-default"> <action name="helloworld" class="me.ztq.action.HelloWorldAction" method="sayHello"> <result name="success">/1.jsp</result> </action> </package> </struts>
struts.xml配置中的包介绍:
在Struts2框架中使用包来管理Action,包的作用和Java中的类包是类似的,它主要用于管理一组业务功能相关的Action。在实际应用中,我们应该把一组业务功能相关的Action放在同一包下。
配置包时必须指定name属性,该name属性值可以任意取名,但必须唯一,它不对应Java的类包。如果其他包要继承该包,必须通过该属性进行引用。包的namespace属性用于定义该包的命名空间,命名空间作为访问该包下Action路径的一部分。如访问上面例子的Action,访问路径为:/test/helloworld.action。namespace属性可以不设置。对本例而言,如果不指定改属性,默认的命名空间为“”(空字符串)。
通常每个包都应该继承struts-default包,因为Struts2很多核心的功能都是拦截器来实现。如:从请求中把请求参数封装到action、文件上传和数据验证等都是通过拦截器来实现的。struts-default定义了这些拦截器和Result类型。当包继承了struts-default才能使用Struts2提供的核心功能。struts-default包是在struts2-core-2.x.x.jar文件中的struts-default.xml中定义。struts-default.xml也是Struts2默认配置文件。Struts2每次都会自动加载struts-default.xml文件。
(2)根据配置文件,创建需要的JavaBean和对应的动作方法,在动作方法中完成逻辑调用。
1 package me.ztq.action; 2 3 import java.io.Serializable; 4 5 public class HelloWorldAction implements Serializable { 6 private String message; 7 8 public String getMessage() { 9 return message; 10 } 11 12 public void setMessage(String message) { 13 this.message = message; 14 } 15 16 public String sayHello(){ 17 message = "helloworld by struts2"; 18 return "success"; 19 } 20 }
(3)编写View,显示结果。
${message}
(4)访问helloworld动作的方式:http://localhost:8080/struts2text/test/helloworld
默认情况下,访问动作名helloworld,可以直接写helloworld也可以helloworld.action
3. struts配置文件详解
(1)action:
class:默认值是com.opensymphony.xwork2.ActionSupport
常量:SUCCESS success
NONE none
ERROR error
INPUT input
LOGIN login
method:默认值是public String execute(){}
实际开发中,自己编写的动作类一般情况下继承ActionSupport类。
(2)result:
type:转到目的地的方式。默认值是转发,名称是dispatcher
(注:type的取值是定义好的,不是随便写的。在struts-default.xml中的package中有定义)
dispatcher:普通的转发到某个页面
redirect:重定向到一个页面
chain:普通的转发到某个动作名称
redirectAction:重定向到一个动作名称
plainText:以纯文本的形式输出JSP内容
result元素的写法:
(一)<result type="chain" name="success">a2</result>
(二)<result type="chain" name="success">
<param name="actionName">a2</param> <!--name对应的是chain处理器中的setActionName()方法-->
</result>
(注:如果要转向的是在另外一个名称空间的动作,那么只能用(二))
如:
<package name="p1" namespace="/namespace1" extends="struts-default"> <action name="a2"> <result type="dispatcher" name="success">/3.jsp</result> </action> </package> <package name="p2" namespace="/namespace2" extends="struts-default"> <action name="a1"> <result type="chain" name="success"> <param name="namespace">/namespace1</param> <param name="actionName">a2</param> </result> </action> </package>
访问:http://localhost:8080/struts2text/namespace2/a1
(3)
开发中配置文件的更改,在访问时让框架自动重新加载:
struts.devMode = false(在default.properties中)
利用struts.xml中的constant元素来覆盖掉default.properties中的默认行为
<struts>
<constant name="struts.devMode" value="true"></constant>
</struts>
指定需要Struts2处理的请求后缀:可以通过常量struts.action.extension进行修改,如:
<struts>
<constant name="struts.action.extension" value="action,,do"></constant>
</struts>
(4)
<!-- 动态的给Action类的属性赋值 --> <package name="ztq" namespace="/test" extends="struts-default"> <action name="a1" class="me.ztq.action.HelloWorldAction"> <param name="message">哈哈</param> <result name="success">/3.jsp</result> </action> </package>
<!-- 获取Action的值 --> <package name="ztq" namespace="/test" extends="struts-default"> <action name="a2" class="me.ztq.action.HelloWorldAction" method="sayHello"> <result type="redirect" name="success">/3.jsp?msg=${message}</result> </action> </package>
(5)全局结果视图配置:
<package name="mypackage" extends="struts-default"> <!-- 配置全局错误结果:范围只是本包 --> <global-results> <result type="dispatcher" name="error">/customer/error.jsp</result> </global-results> </package> <package name="customer" namespace="/customer" extends="mypackage"> <action name="addCustomer" class="me.ztq.action.CustomerAction" method="addCustomer"> <result type="dispatcher" name="success">/customer/success.jsp</result> </action> <action name="updateCustomer" class="me.ztq.action.CustomerAction" method="updateCustomer"> <result type="dispatcher" name="success">/customer/success.jsp</result> </action> </package>
(6)struts2中的常量及设置:
4. 动作类的生命周期:
StrutsPrepareAndExecuteFilter是Struts2框架的核心控制器,它负责拦截由<url-pattern>/*</url-pattern>指定的所有用户请求,当用户请求到达时,该Filter会过滤用户的请求。默认情况下,如果用户请求的路径不带后缀或后缀以.action结尾,这是请求将被转入Struts2框架处理,否则Struts2框架将略过该请求的处理。当请求转入Struts2框架处理时先经过一系列的拦截器,然后再到Action。与Struts1不同,Struts2对用户的每一次请求都会创建一个Action,所以Struts2中的Action是线程安全的。
5. 为应用指定多个struts配置文件:
在大部分应用里,随着应用规模的增加,系统中Action的数量也会大量增加,导致struts.xml配置文件变得非常臃肿。为了避免struts.xml文件过于庞大,提高struts.xml文件的可读性,我们可以将一个struts.xml配置文件分解成多个配置文件,然后在 struts.xml文件中包含其他配置文件,下面的struts.xml通过<include>元素指定多个配置文件:
<struts>
<include file="struts-user.xml" />
<include file="struts-order.xml" />
</struts>
6. 动态方法调用(不建议使用):
如果Action中存在多个方法时,我们可以使用 !+方法名 调用指定方法。如:
假设访问action的URL路径为:/struts/test/helloworld.action
如果要访问其中的other()方法,可以调用:/struts/test/helloworld!other.action
使用通配符进行动态方法调用:
<package name="orders" namespace="/orders" extends="mypackage"> <action name="order_*" class="me.ztq.action.OrderAction" method="{1}"> <result type="dispatcher" name="success">/orders/{1}.jsp</result> </action> </package>
http://localhost:8080/struts/orders/order_add 就会访问add方法,转到add.jsp页面
7. 获取表单请求参数:
(1)采用基本类型接收请求参数(get/post)
在Action类中定义与请求参数同名的属性,struts2便能自动接收请求参数并赋予给同名属性。
请求路径:http://localhost:8080/test/view.action?id=100
1 public class ProductAction{ 2 private Integer id; 3 4 //struts2通过反射技术调用与请求参数同名的属性的setter方法来获取请求属性值 5 public void setId(Integer id){ 6 this.id = id; 7 } 8 public Integer getId(){ 9 return id; 10 } 11 }
(2)采用复合类型接收请求参数
请求路径:http://localhost:8080/test/view.action?product.id=78
1 public class ProductAction{ 2 private ProductAction product; 3 private Integer id; 4 public Integer getId() { 5 return id; 6 } 7 8 public void setId(Integer id) { 9 this.id = id; 10 } 11 12 public ProductAction getProduct() { 13 return product; 14 } 15 16 public void setProduct(ProductAction product) { 17 this.product = product; 18 } 19 20 }
struts2首先通过反射技术调用ProductAction的默认构造函数创建product对象,然后在通过反射技术调用product中与请求参数同名的属性的setter方法来获取请求参数值。
注:关于struts2.1.6接收中文请求参数乱码问题
struts2.1.6版本中存在一个bug,即接收到的中文请求参数为乱码(以post方式提交),原因是struts2.1.6在获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就是乱码请求参数。这个bug在struts2.1.8中已经被解决。如使用的是2.1.6,要解决这个问题,可以新建一个Filter,把这个Filter放置在struts2的Filter之前,然后在doFilter()方法里添加如下代码:
1 public void doFilter(...){ 2 HttpServletRequest req = (HttpServletRequest)request; 3 req.setCharacterEncoding("UTF-8"); 4 filterchain.doFilter(request, response); 5 }
8. 自定义类型转换器:
(1)编写一个类,继承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter
(2)覆盖掉其中的public Object convertValue(Map<String, Object> context, Object value,Class toType)
1 public class DateConvertor extends DefaultTypeConverter { 2 /* 3 context:ognl表达式的上下文 4 value:用户输入的值( 保存数据时)或者模型中的属性。用户输入的值是String数组 5 toType:目标类型 6 */ 7 @Override 8 public Object convertValue(Map<String, Object> context, Object value, 9 Class toType) { 10 DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); 11 if(toType==Date.class){ 12 //2017/04/02----->java.util.Date 保存数据时 13 String strValue = ((String[])value)[0]; 14 try { 15 return df.parse(strValue); 16 } catch (ParseException e) { 17 throw new RuntimeException(e); 18 } 19 }else{ 20 //java.util.Date----->2017/04/02 获取数据时 21 Date dValue = (Date)value; 22 return df.format(dValue); 23 } 24 } 25 }
(3)注册类型转换器
○ 局部类型转换器:只对当前的Action有效
具体做法:在动作类相同的包中,建立一个名称是“动作类名-conversion.properties”的配置文件,文件中增加以下内容:要验证的字段=验证器的类全名,如:birthday=cn.itcast.convertor.DateConvertor
○ 全局类型转换器:对所有的Action都有效
具体做法:在WEB-INF/classes目录下,建立一个名称为"xwork-conversion.properties"的配置文件,文件中增加以下内容:目标类型全名=验证器的类全名,如:java.util.Date=cn.itcast.convertor.DateConvertor
注意:如果转换失败,Struts2框架会寻找name=input的结果页面
9. 获取Servlet相关对象
(1)通过ServletActionContext类直接获取:
1 public String execute(){ 2 ServletContext sc = ServletActionContext.getServletContext(); 3 ServletRequest request = ServletActionContext.getRequest(); 4 return SUCCESS; 5 }
(2)实现指定接口,由struts框架运行时注入:
1 public class testAction extends ActionSupport implements Serializable, ServletContextAware, ServletRequestAware, ServletResponseAware{ 2 private ServletContext context; 3 private HttpServletRequest request; 4 private HttpServletResponse response; 5 public String execute(){ 6 System.out.println(context); 7 System.out.println(request); 8 System.out.println(response); 9 } 10 11 //如果动作类实现了ServletContextAware接口,就会自动调用该方法 12 public void setServletContext(ServletContext context){ 13 this.context = context; 14 } 15 16 public void setServletRequest(HttpServletRequest request){ 17 this.request = request; 18 } 19 20 public void setServletResponse(HttpServletResponse response){ 21 this.response = response; 22 } 23 }
10. 文件上传
第一步、在WEB-INF/lib下加入commons-fileupload-1.2.1.jar、commons-io-1.3.2.jar两个文件
第二步、把form表的enctype设置为:“multipart/form-data”,如下:
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/xxx.action" method="post">
<input type="file" name="uploadImage" />
</form>
第三步、在Action类中添加以下属性:
1 public class HelloWorldAction{ 2 private File uploadImage; //得到上传的文件 3 private String uploadImageContentType; //得到文件的类型 4 private String uploadImageFileName; //得到文件的名称 5 //这里省略了属性的getter/setter方法 6 7 public String upload() throws Exception{ 8 String realPath = ServletActionContext.getServletContext().getRealPath("/images"); 9 File file = new File(realPath); 10 if(!file.exists()) 11 file.mkdirs(); 12 FileUtils.copyFile(uploadImage, new File(file, uploadImageFileName)); 13 return "success"; 14 } 15 }
11. 自定义拦截器
(1)编写一个类,实现com.opensymphony.xwork2.interceptor.Interceptor
(2)主要实现public String intercept(ActionInvocation invocation) throws Exception{}方法,该方法的返回值就相当于动作的返回值。如果调用了String result = invocation.invoke(),就得到了动作类的返回的值。
1 public String intercept(ActionInvocation invocation) throws Exception { 2 //判断用户是否登录 3 HttpSession session = ServletActionContext.getRequest().getSession(); 4 Object obj = session.getAttribute("user"); 5 if(obj==null){ 6 return "login"; 7 }else{ 8 return invocation.invoke();//调用动作方法 9 } 10 }
(3)拦截器定义好后,一定要在配置文件中进行注册:
<interceptors> <!--只是定义拦截器,并没有起作用--> <interceptor name="permissionInterceptor" class="cn.itcast.interceptor.PermissionInterceptor"></interceptor> </interceptors>
(4)配置文件中的动作,要通过<interceptor-ref name="permissionInterceptor"></interceptor-ref>使用该拦截器。
注意:一旦动作中使用了自定义的拦截器,那么默认的就不起作用了。一般应该采用如下的做法:
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="permissionInterceptor"></interceptor-ref>
<interceptors> <interceptor name="permission" class="me.ztq.action.PermissionInterceptor" /> <interceptor-stack name="permissionStack"> <interceptor-ref name="defaultStack" /> <interceptor-ref name="permission" /> </interceptor-stack> </interceptors>
多个动作类都要使用的话,可以通过package来进行组合。
因为struts2中如文件上传,数据验证,封装请求参数到action等功能都是由系统默认的defaultStack中的拦截器实现的,所以我们定义的拦截器需要引用系统默认的defaultStack,这样应用才可以使用struts2框架提供的众多功能。
如果希望包下的所有action都使用自定义的拦截器,可以通过<default-interceptor-ref name="permissionStack" />把拦截器定义为默认拦截器。注意:每个包只能指定一个默认拦截器。另外,一旦我们为该包中的某个action显式指定了某个拦截器,则默认拦截器不会起作用。
13. 用户输入数据验证
在struts2中,我们可以实现对action的所有方法进行校验或者对action的指定方法进行校验。
对于输入校验,struts2提供了两种实现方法:
(1)采用手工编写代码实现,针对该动作类中的所有动作方法
动作类的所有方法进行验证:
- 动作类继承ActionSupport类
- 覆盖掉public void validate()方法
- 在validate方法中,编写不符合要求的代码判断,并调用父类的addFieldError(String fieldName, String errorMessage)方法,如果fieldError(存放错误信息的Map)有任何的元素,就是验证不通过,动作方法不会执行。struts2框架会返回到name=input的result
- 在name=result指定的页面上使用struts2的标签显示错误信息<%@ taglib uri="/struts-tags" prefix="s"%> <s:fielderror />
动作类的指定方法进行验证:
- 编写步骤与上面相同
- 验证方法书写有要求:public void validateXxx() Xxx代表的是要验证的动作方法名,其中要把动作方法名的首字母变为大写。
(2)基于XML配置方式实现
动作类的所有方法进行验证:
使用基于XML配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和Action类放在同一个包中,文件的取名格式为:ActionClassName-validation.xml,其中ActionClassName为Action的简单类名,-validation为固定写法。如果Action类为me.ztq.UserAction,那么该文件的取名应为:UserAction-validation.xml。下面是校验文件的模板:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd"> <validators> <field name="username"> <!-- 内置验证器都是定义好的,在xwork-core.jar com.opensymphony.xwork2.validator.validators包中的default.xml文件中 --> <field-validator type="requiredstring"><!-- 不能为null或者""字符串,默认会去掉前后的空格 --> <message>用户名不能为空</message> </field-validator> </field> </validators>
<field name="password"> <field-validator type="requiredstring"> <message>密码不能为空</message> </field-validator> <field-validator type="regex"> <param name="expression"><![CDATA[\d{3, 6}]]></param> <message>密码必须是3~6位数字</message> </field-validator> </field>
动作类的指定方法进行验证:
配置文件的名称书写有一定要求:动作类名-动作名(配置文件中的)-validation.xml
(3)自定义基于XML的验证器:
- 编写一个类,继承FieldValidatorSupport类
- 在public void validate(Object object)编写验证逻辑,不符合要求的就向fieldErrors中放消息
- 一定要注册验证器后才能使用,在WEB-INF/classes目录下建立一个名称为validators.xml的配置文件,内容如下:
<validators> <validator name="strongpassword" class="cn.itcast.validators.StrongPasswordValidator"/> </validators>
- 以后就可以像使用Struts2提供的16个验证器方式去使用了。
StrongPasswordValidator.class
1 package me.ztq.validators; 2 3 import java.io.Serializable; 4 5 import com.opensymphony.xwork2.validator.ValidationException; 6 import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport; 7 8 public class StrongPasswordValidator extends FieldValidatorSupport implements 9 Serializable { 10 11 private int minLength = -1; 12 13 public int getMinLength() { 14 return minLength; 15 } 16 17 public void setMinLength(int minLength) { 18 this.minLength = minLength; 19 } 20 21 public void validate(Object object) throws ValidationException { 22 String fieldName = getFieldName(); //取得字段名 23 String fieldValue = (String)getFieldValue(fieldName, object); //取得用户输入的当前字段的值 24 if(!isPasswordStrong(fieldValue)){ 25 addFieldError(fieldName, object); 26 } 27 } 28 29 private static final String GROUP1 = "abcdefghijklmnopqrstuvwxyz"; 30 private static final String GROUP2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 31 private static final String GROUP3 = "0123456789"; 32 private boolean isPasswordStrong(String password) { 33 boolean ok1 = false; 34 boolean ok2 = false; 35 boolean ok3 = false; 36 int length = password.length(); 37 for(int i = 0; i < length; i++){ 38 if(ok1 && ok2 && ok3) 39 break; 40 String character = password.substring(i, i + 1); 41 if(GROUP1.contains(character)){ 42 ok1 = true; 43 continue; 44 } 45 if(GROUP2.contains(character)){ 46 ok2 = true; 47 continue; 48 } 49 if(GROUP3.contains(character)){ 50 ok3 = true; 51 continue; 52 } 53 } 54 return ok1 && ok2 && ok3; 55 } 56 57 }
14. 国际化
准备资源文件, 资源文件的命名格式如下:
baseName_language_country.properties
baseName_language.properties
baseName.properties
其中baseName是资源文件的基本名,可以自定义,但language和country必须是Java支持的语言和国家。如:
中国大陆:baseName_zh_CN.properties
美国:baseName_en_US.properties
(1)全局资源文件:放到WEB-INF/classes目录下
准备好资源文件后,可以在struts.xml中通过struts.custom.i18n.resources常量把资源文件定义为全局资源文件,如下:
<constant name="struts.custom.i18n.resources" value="me" /> me为资源文件的基本名。
以后就可以在页面或在action中访问国际化信息:
- 在JSP页面中使用<s:text name="" />标签输出国际化信息:<s:text name="user" />,name为资源文件中的key
- 在Action类中,可以继承ActionSupport,使用getText()方法得到国际化信息,该方法的第一个参数用于指定资源文件中的key
- 在表单标签中,通过key属性指定资源文件中的key,如:<s:textfield name="realname" key="user" />
(2)包范围资源文件:服务于Java类的包下的动作类
取名:package_语言_国家.properties
在Java的包下放置package_语言_国家.properties资源文件,package为固定写法,处于该包及子包下的action都可以访问该资源。当查找指定key的消息时,系统会从package资源文件查找,当找不到对应的key时,才会从常量struts.i18n.resources指定的资源文件中寻找。
(3)Action范围资源文件:
在Action类所在的路径,放置ActionClassName_language_country.properties资源文件,ActionClassName为action类的简单名称。
当查找指定key的消息时,系统会先从ActionClassName_language_country.properties资源文件查找,如果没有找到对应的key,然后沿着当前包往上查找基本名为package的资源文件,一直找到最顶层包。如果还没有对应的key,最后从常量struts.i18n.resources指定的资源文件中寻找。
在JSP中直接访问某个资源文件:
struts2提供了<s:i18n>标签,使用<s:i18n>标签我们可以再类路径下直接从某个资源文件中获取国际化数据,而无需任何配置:
<s:i18n name="ztq">
<s:text name="welcome" />
</s:i18n>
ztq为类路径下资源文件的基本名。如果要访问的资源文件在类路径的某个包下,可以这样访问:
<s:i18n name="me/ztq/action/package">
<s:text name="welcome">
<s:param>呵呵</s:param>
</s:text>
</s:i18n>
上面访问me.ztq.action包下基本名为package的资源文件。
15. OGNL表达式
(1)OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目。Struts2框架使用OGNL作为默认的表达式语言。
相对EL表达式。它提供了平时我们需要的一些功能,如:
- 支持对象方法调用,如:XXX.sayHello();
- 支持类静态方法调用和值访问,表达式的格式为@类全名(包括包路径)@方法名(或者值名)。例如:@java.lang.String@format('foo %s', 'bar')或@me.ztq.Constant@APP_NAME
- 操作集合对象
- OGNL有一个上下文(Context)概念,就是一个Map结构,它实现了java.utils.Map接口,struts2中上下文(Context)的实现为ActionContext。
- 当struts接受一个请求时,会迅速创建ActionContext,ValueStack,action。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
- 访问上下文(Context)中的对象需要使用#符号标注命名空间,如:#application、#session
- 另外OGNL会设定一个根对象(root对象),在struts2中根对象就是ValueStack(值栈)。访问根对象(即ValueStack)中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。
- 在Struts2中,根对象ValueStack的实现类为OgnlValueStack,该对象不是我们想象的只存放单个值,而是存放一组对象。在OgnlValueStack类里有一个List类型的root变量,就是使用它存放一组对象。
- Context----OnglValueStack root变量[action, OgnlUtil, ...]
- 在root变量中处于第一位的对象叫栈顶对象。通常我们在OGNL表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。
综上:
ActionContext的结构:
ValueStack:
List:动作类放在此处。取存放在ValueStack中的root的对象的属性,直接写即可。访问以下内容中的对象要使用#+范围
application:用于访问ServletContext,例如:#application.userName或者application['userName'],相当于调用ServletContext的getAttribute("username")。
session:用于访问HttpSession
request:用于访问HttpServletRequest
parameters:用于访问HTTP的请求参数
attr:用于按page->request->session->application顺序访问其属性
注:struts2中,OGNL表达式需要配合struts标签才可以使用,如:<s:property value="name" />;除此之外,在页面中可以使用<s:debug />查看上下文中的对象
(2)采用OGNL表达式创建List/Map集合对象:
如果需要一个集合元素的时候(例如List对象或者Map对象),可以使用OGNL中同集合相关的表达式。使用如下代码直接生成一个List对象:
<s:set var="list" value="{'a', 'b', 'c'}" />
<s:iterator value="#list">
<s:property /><br />
</s:iterator>
Set标签用于将某个值放入指定范围。
scope:指定变量被放置的范围,该属性可以接受application、session、request、page或action。如果没有设置该属性,则默认放置在OGNL Context中。
value:赋给变量的值。如果没有设置该属性,则将ValueStack栈顶的值赋给变量。
生成一个Map对象:
<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" />
<s:iterator value="#foobar" var="me">
<s:property value="#me.key" />=<s:property value="#me.value" /><br />
</s:iterator>
也可以:
<c:forEach items="${foobar}" var="me">
${me.key} = ${me.value} <br />
</c:forEach>
(3)采用OGNL表达式判断对象是否存在于集合中
对于集合类型,OGNL表达式可以使用in和not in两个元素符号。其中,in表达式用来判断某个元素是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,如:
in表达式:
<s:if test="foo in {'foo', 'bar'}">
在
</s:if>
<s:else>
不在
</s:else>
not in表达式:
<s:if test="foo not in {'foo', 'bar'}">
不在
</s:if>
<s:else>
在
</s:else>
(4)OGNL表达式的投影功能
OGNL还允许使用某个规则获得集合对象的子集,常用的有以下3个相关操作符
?:获得所有符合逻辑的元素
^:获得符合逻辑的第一个元素
$:获得符合逻辑的最后一个元素
如:
<s:iterator value="books.{?#his.price>35}">
<s:property value="title"/>-$<s:property value="price" /><br />
</s:iterator>
在上面的代码中,直接在集合后紧跟.{}运算符表明用于取出该集合的子集,{}内的表达式用于获取符合条件的元素,this指的是为了从大集合books筛选数据到小集合,需要对大集合books进行迭代,this代表当前迭代的元素。本例的表达式用于获取集合中价格大于35的书集合
1 public class BookAction extends ActionSupport{ 2 private List<Book> books; 3 public String execute(){ 4 books = new ArrayList<Book>(); 5 books.add(new Book("a", "spring", 67)); 6 books.add(new Book("b", "ejb", 15)); 7 } 8 }
16. 常用标签:
(1)property
property标签用于输出指定值:
<s:set name="name" value="kk" />
<s:property value="#name" />
default可选属性,如果需要输出的属、.性值为null,则显示该属性指定的值
escape可选属性,指定是否格式化HTML代码
value可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值
id可选属性,指定该元素的标识。(过时)
(2)Iterator标签用于对集合进行迭代,这里的集合包含List、Set和数组
<s:set name="list" value="{'a', 'b', 'c'}" /> <s:iterator value="#list" status="st"> <font color=<s:if test="#st.odd">red</s:if><s:else>blue</s:else>> <s:property /></font><br /> </s:iterator>
value:可选属性,指定被迭代的集合,如果没有设置该属性,则使用ValueStack栈顶的集合。
id:可选属性,指定该元素的标识。(过时)
status:可选属性,该属性指定迭代时的IterateStatus实例。该实例包含如下几个方法:
int getCount(),返回当前迭代了几个元素。
int getIndex(),返回当前迭代元素的索引。
boolean isEven(),返回当前被迭代元素的索引是否是偶数。
boolean isOdd(),返回当前被迭代元素的索引是否是奇数。
boolean isFirst(),返回当前被迭代元素是否是第一个元素。
boolean isLast(),返回当前被迭代元素是否是最后一个元素。
如:
<body> <s:set var="records" value="{'a', 'b', 'c', 'd'}" /> <table border="1"> <tr> <th>序号</th> <th>书名</th> </tr> <s:iterator value="records" status="st"> <tr> <td> <s:property value="#st.count" /> </td> <td> <s:property /> </td> </tr> </s:iterator> </table> </body>
(3)url标签
<s:url action="helloworld_add" namespace="/test">
<s:param name="personid" value="23">
</s:url>
生成类似如下路径:
/struts/test/helloworld_add.action?personid=23
当标签的属性值作为字符串类型处理时,“%”符号的用途是计算OGNL表达式的值
<s:set name="myurl" value="http://www.haha.net" />
<s:url value="myurl" /><br />
<s:url value="%{#myurl}" />
输出结果:
#myurl
http://www.haha.net
(4)checkboxlist复选框
如果集合为list
<s:checkboxlist name="list" list="{'Java', '.Net', 'RoR', 'PHP'}" value="{'Java', '.Net'}" />
生成如下Html代码:
<input type="checkbox" name="list" value="Java" checked="checked" /><label>Java</label>
<input type="checkbox" name="list" value=".Net" checked="checked" /><label>.Net</label>
<input type="checkbox" name="list" value="RoR" /><label>RoR</label>
<input type="checkbox" name="list" value="PHP" /><label>PHP</label>
如果集合为Map
<s:checkboxlist name="map" list="#{1:'瑜伽用品', 2:'户外用品', 3:'球类', 4:'自行车'}" listKey="key" listValue="value" value="{1, 2, 3}" />
生成如下html代码:
<input type="checkbox" name="map" value="1" checked="checked" /><label>瑜伽用品</label>
<input type="checkbox" name="map" value="2" checked="checked" /><label>户外用品</label>
<input type="checkbox" name="map" value="3" checked="checked" /><label>球类</label>
<input type="checkbox" name="map" value="4" /><label>自行车</label>
(5)<s:token />标签防止重复提交
在表单中加入<s:token />
<body> <s:form action="addCustomer" namespace="/customer"> <s:token></s:token> <s:textfield name="username" label="用户名" /> <s:submit value="保存" /> </s:form> </body>
<action name="addCustomer" class="me.ztq.action.CustomerAction" method="add"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="token"></interceptor-ref> <result name="invalid.token">/success.jsp</result> <result name="success">/success.jsp</result> </action>
以上配置加入了“token”拦截器和“invalid.token”结果,因为“token”拦截器在会话的token与请求的token不一致时,将会直接返回“invalid.token”结果。
在Debug状态,控制台出现下面信息,是因为Action中并没有struts.token和struts.token.name属性,不用关心这个错误