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属性,不用关心这个错误

 

posted @ 2017-04-25 14:30  Chris_z  阅读(175)  评论(0编辑  收藏  举报