**strut2之Action的数据

来源:互联网 发布:java开发案例 编辑:程序博客网 时间:2024/05/04 09:47

一、数据来源
在helloworld示例里面,在运行Action的execute方法的时候,你会神奇般的发现,Action的属性是有值的,而这正是Action进行请求处理所需要的数据。那么,这些数据从何而来呢?
很明显,这些数据就是你在登录页面填写的数据,换句话说,这些数据来源于用户请求对象,也就是request对象。
可是,Struts2怎么知道,页面上的值如何和Action的属性进行对应呢?
这就涉及到如何把页面的数据和Action进行对应的问题了,接下来就来讨论页面的数据和Action的三种基本对应方式。
二、 基本的数据对应方式
在Struts2中,页面的数据和Action有两种基本对应方式,分别是:属性驱动(FieldDriven)和模型驱动(ModelDriven)
属性驱动又有两种情况:一种是基本数据类型的属性对应;另外一种是JavaBean风格的属性对应。为了区分它们,我们约定称呼如下:称呼“基本数据类型的属性对应”为属性驱动,而“JavaBean风格的属性对应”为直接使用域对象。
下面就分别来看看它们都什么意思,都如何实现。
1:属性驱动FieldDriven(基本数据类型的属性对应)
基本数据类型的属性对应,就是web页面上要提交的html控件的name属性,和Action的属性或者与属性相应的getter/setter相对应,这种做法就是基本数据类型的属性对应的属性驱动。
事实上,我们已经使用过这种方式了,前面HelloWorld示例,就是采用的这种方式来把值对应到Action中的。
比如在登录页面上,我们是这么写的:

<form action="/helloworld/helloworldAction.action" method="post">      <input type="hidden" name="submitFlag" value="login"/>      账号:<input type="text" name="account"><br>      密码:<input type="password" name="password"><br>      <input type="submit" value="提交">  </form>  

在Action中是这么写的:

public class HelloWorldAction extends ActionSupport {      private String account;      private String password;      private String submitFlag;      public String getAccount() {          return account;      }      public void setAccount(String account) {          this.account = account;      }      public String getPassword() {          return password;      }      public void setPassword(String password) {          this.password = password;      }      public String getSubmitFlag() {          return submitFlag;      }      public void setSubmitFlag(String submitFlag) {          this.submitFlag = submitFlag;      }      //其他部分暂时省略掉,好让大家看清楚数据的对应关系  }  

你会发现,在页面上input的name属性,和Action的属性是同一个名称,这样一来,当页面提交的时候,Struts2会自动从request对象里面把数据取出来,然后按照名称进行对应,自动设置到Action的属性里面去。
有些朋友可能会说,Action的属性都是private的呀,按道理外部是无法访问的,正是因为如此,才为每个私有的属性提供了getter/setter方法,来让外部访问。
这也意味着,如果你不想为每个属性提供getter/setter方法,觉得很累赘,有一个简单的方式,那就是把属性的可访问权限设置成public的就可以了。但在Java开发中,不是很建议直接开放属性让外部访问,一般都是通过getter/setter方法来访问。当然如何选择,根据实际情况来判断吧,总之两种方式都是可以把值对应上的。
2:属性驱动FieldDriven(直接使用域对象)
仔细察看上面属性驱动的方式,会发现,要是需要传入的数据很多的话,那么Action的属性也就很多了,再加上对应的getter/setter方法,Action类就直接上百行了,再在里面写请求处理的代码,会显得Action非常零乱,不够简洁,而且给人的感觉是Action的功能也不够单一。那么该怎么解决这个问题呢?
很简单,把属性和对应的getter/setter方法从Action里面移出去,单独做成一个域对象,这个对象就是用来封装这些数据的,然后在Action里面直接使用这个对象就可以了。
(1)先看看域对象的写法,按照JavaBean的风格来写,示例代码如下:

public class HelloWorldModel {      private String account;      private String password;      private String submitFlag;      public String getAccount() {          return account;      }      public void setAccount(String account) {          this.account = account;      }      public String getPassword() {          return password;      }      public void setPassword(String password) {          this.password = password;      }      public String getSubmitFlag() {          return submitFlag;      }      public void setSubmitFlag(String submitFlag) {          this.submitFlag = submitFlag;      }  }  

(2)看看此时,Action写法的变化,主要就是直接使用这个对象,其实就是定义一个属性是这个对象类型,然后为这个属性提供相应的getter/setter方法即可,当然也可以直接把这个属性的可访问属性设置成public,这样就不需要写getter/setter方法了。
原来Action里面直接使用属性值的地方,就修改成使用这个属性对象来获取值了。示例代码如下:

public class HelloWorldAction extends ActionSupport {      private HelloWorldModel hwm = new HelloWorldModel();      public HelloWorldModel getHwm() {          return hwm;      }      public void setHwm(HelloWorldModel hwm) {          this.hwm = hwm;      }      public String execute() throws Exception {          //1:收集参数,不用做了,数据会直接映射到上面的hwm里面          //2:组织参数,也不用作了,数据会映射到上面的hwm的时候,就已经组织好了          //3:调用模型的逻辑功能处理,这里不需要,只是简单的输出一下传入的参数          this.businessExecute();          //4:根据逻辑处理的结果来选择下一个页面,这里直接选择转向欢迎页面          return "toWelcome";      }      public void validate(){          if(hwm.getAccount()==null || hwm.getAccount().trim().length()==0){              this.addFieldError("account", this.getText("k1"));          }          if(hwm.getPassword()==null || hwm.getPassword().trim().length()==0){              this.addFieldError("password",  this.getText("k2"));          }          if(hwm.getPassword()==null || hwm.getPassword().trim().length()<6){              this.addFieldError("password",  this.getText("k3"));          }      }      /**      * 示例方法,表示可以执行业务逻辑处理的方法,      */      public void businessExecute(){          System.out.println("用户输入的参数为==="+"account="+hwm.getAccount()+",password="+hwm.getPassword()+",submitFlag="+hwm.getSubmitFlag());      }     }  

(3)Action发生变化后,登录页面上也需要相应改变,否则数据是无法正确对应的,主要是在相应的name属性上,添加一个域对象的前缀,指明这个值到底对应到哪一个域对象里面去,示例如下:

<form action="/helloworld/helloworldAction.action" method="post">      <input type="hidden" name="hwm.submitFlag" value="login"/>      账号:<input type="text" name="hwm.account"><br>      密码:<input type="password" name="hwm.password"><br>      <input type="submit" value="提交">  </form>  

同理欢迎页面也需要相应调整,示例如下:

欢迎账号为<s:property value="hwm.account"/>的朋友来访 

好了,去测试一下看看,是否好用。
3:模型驱动ModelDriven
在Struts2中,还有另外一种对应数据的方式叫模型驱动ModelDriven。它的基本实现方式是让Action实现一个ModelDriven的接口,这个接口需要我们实现一个getModel的方法,这个方法返回的就是Action所使用的数据模型对象。
(1)把Action代码修改成ModelDriven的实现方式,只是添加了ModelDriven的实现,另外去掉了“hwm”属性对应的getter/setter方法,其他地方基本上没有什么变化,示例代码如下:

import com.opensymphony.xwork2.ActionSupport;  import com.opensymphony.xwork2.ModelDriven;  public class HelloWorldAction extends ActionSupport implements ModelDriven{      private HelloWorldModel hwm = new HelloWorldModel();          public Object getModel() {          return hwm;      }      public String execute() throws Exception {          this.businessExecute();          return "toWelcome";      }      public void validate(){          if(hwm.getAccount()==null || hwm.getAccount().trim().length()==0){              this.addFieldError("account", this.getText("k1"));          }          if(hwm.getPassword()==null || hwm.getPassword().trim().length()==0){              this.addFieldError("password",  this.getText("k2"));          }          if(hwm.getPassword()==null || hwm.getPassword().trim().length()<6){              this.addFieldError("password",  this.getText("k3"));          }      }      /**      * 示例方法,表示可以执行业务逻辑处理的方法,      */      public void businessExecute(){          System.out.println("用户输入的参数为==="+"account="+hwm.getAccount()+",password="+hwm.getPassword()+",submitFlag="+hwm.getSubmitFlag());      }  }  

(2)登录页面也需要做相应调整,主要就是去掉刚才给name属性添加的“hwm.”这个前缀,示例代码如下:

<form action="/helloworld/helloworldAction.action" method="post">      <input type="hidden" name="submitFlag" value="login"/>      账号:<input type="text" name="account"><br>      密码:<input type="password" name="password"><br>      <input type="submit" value="提交">  </form>  

同理去调整欢迎页面,这里就不去示范了。
那么这里为什么不需要前缀了呢?
原因很简单,使用ModelDriven的方式,一个Action只能对应一个Model,因此不需要添加前缀,Struts2就能够知道,页面上“account”的值就对应到这个Model的“account”属性。如果你去加上前缀,反而对应不上了。
4:小结
(1)这里学习了三种数据的对应方式,在实际开发中该如何选择呢?
下面简要分析一下:
属性驱动(基本数据类型的属性对应):优点:简单,页面name和属性直接对应;缺点:导致Action类看上去比较零乱,显得功能不够单一。因此在实际开发中会酌情使用。
属性驱动(直接使用域对象):优点:把模型数据从Action中分离出来,让Action专注于请求处理,使得程序结构更清晰;缺点:页面上在对应的时候,必须添加正确的前缀,稍嫌麻烦。
但正是因为有前缀,在一个Action有多个数据模型的时候,这个缺点反而变成了优点,因为可以根据前缀来区分到底把这个数据对应给谁,这样一来,就不会乱了,比如:“hwm.uuid”、“um.uuid”就表示hwm和um这两个模型里面都有一个uuid的属性,但是,现在是带着前缀来指定值的对应,就不会出错了。在实际开发中,推荐优先使用这个方式。
模型驱动:优点:把模型数据从Action中分离出去了,使得程序结构更清晰;缺点:需要Action实现特殊的接口,而且把模型数据和Action作了一个绑定,这极大地限制了一个Action对应多个数据模型的能力,当然也可以做到,就是在这个模型里面包含其他的数据模型。在实际开发中,根据情况来选用。
(2)又有新问题了,这三种方式能不能混合使用呢?如果能?会不会冲突呢?
事实上,这三种方式是可以混合使用,甚至是三种方式一起使用。但是属性驱动(基本数据类型的属性对应)和模型驱动是有可能冲突的,因为这两种对应方式都没有前缀,如果出现这种冲突的情况,那么优先模型驱动的对应方式。
还是举个例子来说明,如果在Action中同时出现三种方式,示例代码如下:

public class HelloWorldAction extends ActionSupport implements ModelDriven{      /**      * 用于ModelDriven使用      */      private HelloWorldModel hwm = new HelloWorldModel();      /**      * 用于域对象的方式使用      */      public HelloWorldModel hwm2 = new HelloWorldModel();      /**      * 用于FieldDriven使用      */      public String account = "";      public Object getModel() {          return hwm;      }         public String execute() throws Exception {          System.out.println("模型驱动的值:account="+hwm.getAccount()+",password="+hwm.getPassword()+",submitFlag="+hwm.getSubmitFlag());          System.out.println("使用域对象的值:account="+hwm2.getAccount()+",password="+hwm2.getPassword()+",submitFlag="+hwm2.getSubmitFlag());          System.out.println("属性驱动的值:account="+account);          return "toWelcome";      }  }  

此时登录页面修改成如下示例:

<form action="/helloworld/helloworldAction.action" method="post">      <input type="hidden" name="submitFlag" value="login"/>      账号:<input type="text" name="account"><br>      密码:<input type="password" name="hwm2.password"><br>      <input type="submit" value="提交">  </form>  

注意,在上述页面的写法中,name=”submitFlag”的值,将会使用ModelDriven的方式对应,因为没有其他可供它对应的地方;name=”account”的值,既可以对应到Action的account属性,也可以通过ModelDriven的方式对应到hwm的account属性;而name=”hwm2.password”的值,只能按照域对象对应的方式,对应到hwm2里面的password属性去。
去运行一下,看看结果。结果示例如下:

模型驱动的值:account=test,password=null,submitFlag=login  使用域对象的值:account=null,password=test,submitFlag=null  属性驱动的值:account=  

你会发现,password直接对应到了域对象的password去,毫无争议;而account的值,虽然可以同时对应到模型和属性上,但结果很明显是模型驱动优先,也就是对应到模型的account属性去了。
(3)学到这里,已经掌握了Action类的写法,掌握了Action里面execute方法的写法,也掌握了如何把值跟Action对应起来,看起来,知识好像足够多了。
但是在实际开发中,往往不会像前面示例得这么简单,而是需要面对各种复杂的情况,比如:
传入值的类型不一致,需要转换
需要传入一组数目不确定的字符串。这在web开发中是非常常见的,比如在注册用户的时候,可能需要添您的爱好,在一系列checkbox框中勾选出您喜欢的。
需要传入一组数目不确定的域对象。比如在旅游类的电子商务应用中,添加一个旅游团之后,还需要把所有的参团人员的基本信息添入。
等等问题,那么接下来就来深入的讨论一下。
三、 传入非String类型的值
前面的示例,从页面传入Action的值都是String类型的,可是在实际开发中,并不是每次传递的数据都是String类型,也可能需要传递别的类型的值,比如传递int类型,好在Struts2能帮助我们完成从String类型到基本类型的自动转换。
1:传入基本类型的值
假如把Action的account改成int类型的,那么该如何对应呢?注意这里只是用int类型来做个示例,其他基本类型也是一样的做法。
(1)此时Action的示例代码如下:

public class HelloWorldAction extends ActionSupport{      public int account;      public String password="";      public String submitFlag ="";         public String execute() throws Exception {          System.out.println("the account="+account+",password="+password+",submitFlag="+submitFlag);          return "toWelcome";      }  }  

(2)此时的登录页面很简单,不需要任何特殊的处理,示例代码如下:

<form action="/helloworld/helloworldAction.action" method="post">      <input type="hidden" name="submitFlag" value="login"/>      账号:<input type="text" name="account"><br>      密码:<input type="password" name="password"><br>      <input type="submit" value="提交">  </form> 

(3)重新访问登录页面,记得在账号的文本框里面填写数字,填写后点击提交,看看后台输出的值:

the account=11,password=22,submitFlag=login  

看上去一切很好,Struts2已经正确的帮我们把request中account的字符串转换成int类型了。
(4)但是,如果在登录页面上不填账号,再次运行一下,会发现后台打印了好多好多错误,如下(错误太多,省略了其中的大部分):

ognl.OgnlException: account [java.lang.IllegalArgumentException: Can not set int   field cn.javass.action.action.HelloWorldAction.account to java.lang.String]          at ognl.ObjectPropertyAccessor.setPossibleProperty(ObjectPropertyAccesso  r.java:103)          ......省略了  Caused by: java.lang.IllegalArgumentException: Can not set int field cn.javass.a  ction.action.HelloWorldAction.account to java.lang.String          ......省略了          ... 62 more  /-- Encapsulated exception ------------\  java.lang.IllegalArgumentException: Can not set int field cn.javass.action.actio  n.HelloWorldAction.account to java.lang.String          ......省略了  \--------------------------------------/  the account=0,password=22,submitFlag=login  

先看看上面加粗的以“Caused by”开头的那句描述,很明确的表明是在设置int型的account属性时出现错误,因为这次页面没有填写account的值,那么传递过来就是一个空字符串或者是null,但不管是哪种情况,都无法转换成为int类型的值,因此就出错了。
再看看最后一句输出,可以得到结论,虽然对应account的值出错了,但是不影响其他属性的取值,password和submitFlag能正确取到值。
因此,如果属性采用基本类型的时候,如果用户没有填写则会抛错,不过这个错误并不影响其他属性值的对应。
2:使用包装类型
现在,再用Integer来试一试,看看会出现什么情况。
(1)登录页面不需变化,只是把Action中的account属性的类型改为Integer,同时把它变成private的,然后提供相应的getter/setter方法,示例如下:

public class HelloWorldAction extends ActionSupport{      private Integer account;      public String password="";      public String submitFlag ="";         public String execute() throws Exception {          System.out.println("the account="+account+",password="+password+",submitFlag="+submitFlag);          return "toWelcome";      }      public Integer getAccount() {          return account;      }      public void setAccount(Integer account) {          this.account = account;      }     }  

(2)重新访问登录页面,在账号的文本框里面填写数字,填写后点击提交,看看后台输出的值,没有任何问题,仍然会正常输出:

the account=111,password=222,submitFlag=login  

(3)接下来,不填写账号,再次运行,看看会怎样呢?
后台运行不再报错,同样能输出值,只是account的值为null而已,如下:

the account=null,password=222,submitFlag=login  

这说明如果使用包装类型的话,就无需关心或者去特别处理Struts2在对应值的时候,自动类型转换所报出的错误了。
(4)可能有些朋友会想,account类型改为Integer后,为什么要为它添加getter/setter方法呢?你可以不去添加getter/setter方法,而是让account为public的,试试看,应该会抛出如下错误:

ognl.NoSuchPropertyException: cn.javass.action.action.HelloWorldAction.account          at ognl.ObjectPropertyAccessor.setProperty(ObjectPropertyAccessor.java:1  66)  ……省略了  

这个错误的意思是:没有找到一个叫做account的property,注意这里用了property而不是直接翻译成“属性”,是因为这里有一个准确理解的问题。
在日常开发中,可能大家并不去关心“property”的准确含义,一般都是当作属性理解,那么“attribute”呢?也是当作“属性”理解吧,那么他们有什么区别呢?
做过设计的朋友可能会很清楚,“property”和“attribute”是不同的。简单点说,“attribute”是用来描述对象固有的一些属性,一般是创建过后不变的一些值,比如:人这个对象,有手这个“attribute”,正常情况下,创建一个人的实例对象过后,手这个属性一般就不变了。因此“attribute”通常就表现成为私有的属性。
而“property”也是属性,但是一般是创建过后可变的一些值,比如:人这个对象,有一个头发颜色这个“property”,创建对象实例过后,这个人可能去染发了,变成其他颜色了,也就是这个属性的值是可以通过外部来改变的。因此“property” 通常就表现成为私有的属性,并为它设置相应的getter/setter方法。
好了,现在来理解上面那个错误的意思,“没有找到一个叫做account的property”,这就明确告诉我们了,account不是一个“property”,也就是说account这个属性没有相应的getter/setter方法。
四、 如何处理传入多个值
在实际开发中,同一个属性需要传入多个值的情况也是很常见的。下面就来讨论一下,看看到底如何处理这种情况。
1:传入一组数目不确定的字符串
比如在注册用户的时候,可能需要添用户的爱好,也就是在一系列文本框中选出用户喜欢做的事情。页面示例如下:

<input type="checkbox" name=" habits" value="sports">运动  <input type="checkbox" name=" habits" value="reading">读书  <input type="checkbox" name=" habits" value="sleep">睡觉  

注意:“habits”在传入action的时候并不知道到底有几个值,可能是一个值,也可能是多个值,如果用户选择了其中两个,就会得到一个有两个字符串的数组。
那么,Action中该如何写才能正确接收这些值呢?
在这种情况下,Action中可以有以下两种写法来对应:
(1)定义一个私有的String数组类型的属性,提供相应的getter/setter方法,示例如下:

private String[] habits;  public String[] getHabits () {      return habits;  }  public void setHabits (String[]habits) {      this. habits= habits;  }  

当然直接定义一个public的String数组类型的属性也可以,示例如下:

public String[] habits;  

(2)定义一个私有的集合类型的属性,比如List类型的,提供相应的getter/setter方法,示例如下:

private List<String> habits;  public List<String> getHabits () {      return habits;  }  public void setHabits (List<String> habits) {      this. habits = habits;  }  

当然直接定义一个public的集合类型的属性也可以,示例如下:

public List<String> hobis; 

2:传入一组数目不确定的域对象
这种情况也很常见,比如添加一个旅游团之后,还要把所有参团人员的基本信息添加到后台,要求Action把每个参团人员的基本信息当作一组,封装成一个域对象。
(1)假设这个域对象名称为UserModel,在Action中定义一个private的List,并给出相应的getter/setter方法,示例代码如下:

private List<UserModel> users;  public List<UserModel> getUsers() {      return users;  }  public void setUsers(List<UserModel> users) {      this.users = users;  }  

(2)这时候在页面上要按照如下的示例来写:

<input type="text" name="users[0].account">  <input type="text" name="users[0].password">  <input type="text" name="users[1].account">  <input type="text" name="users[1].password">  

要注意上面的写法,“属性名称[索引]”。上面这样写Struts2就会把上面的4个文本框组成两个UserModel,第1个和第2个一组,第3个和第4个一组。
(3)除了使用private的List及其getter/setter之外,同样还可以使用public的List,示例代码如下:

public List<UserModel> users = new ArrayList<UserModel>();  

注意,使用public的List的时候,必须在声明的时候就新建一个ArrayList,否则运行会报“NullPointerException”。
(4)另外一点,如果在页面上忘了写索引,如下:

<input type="text" name="users.account">  <input type="text" name="users.password">  <input type="text" name="users.account">  <input type="text" name="users.password">  

那么Action接到的将不是两个对象,而是四个,分别拥有一个属性的值。

http://sishuok.com/forum/blogPost/list/4047.html

0 0
原创粉丝点击