Action组件有3个作用:
1. 封装工作单元:Action主要的职责是控制业务逻辑,Action组件使用execute()方法来控制业务逻辑,这个方法只关注与请求有关的工作逻辑。Action包含业务逻辑,至少是业务逻辑的入口点,并且动作应该让业务逻辑尽可能的简单。
2. 为数据转移提供场所:动作是框架模型组件,意味着动作可以携带数据。动作只需要把期望承载的数据实现为JavaBean属性,来源于表单的请求参数被框架自动的放入到动作中名字匹配的属性上(按方法名)。
3. 返回一个结果(控制字符串):Action组件的最后一个职责是返回控制字符串以让结果路由选择应该被呈现的结果页面。
Action组件的实现:
说明:Struts 2 的Action组件不必一定要实现Action接口。任何对象都可以通过实现一个返回控制字符串的execute()方法来非正式的实现Action组件。
1. 实现Action接口创建Action组件
Action接口的源代码:
public interface Action {
/**
* The action execution was successful. Show result
* view to the end user.
*/
public static final String SUCCESS = "success";
/**
* The action execution was successful but do not
* show a view. This is useful for actions that are
* handling the view in another fashion like redirect.
*/
public static final String NONE = "none";
/**
* The action execution was a failure.
* Show an error view, possibly asking the
* user to retry entering data.
*/
public static final String ERROR = "error";
/**
* The action execution require more input
* in order to succeed.
* This result is typically used if a form
* handling action has been executed so as
* to provide defaults for a form. The
* form associated with the handler should be
* shown to the end user.
* <p/>
* This result is also used if the given input
* params are invalid, meaning the user
* should try providing input again.
*/
public static final String INPUT = "input";
/**
* The action could not execute, since the
* user most was not logged in. The login view
* should be shown.
*/
public static final String LOGIN = "login";
/**
* Where the logic of the action is executed.
*
* @return a string representing the logical result of the execution.
* See constants in this interface for a list of standard result values.
* @throws Exception thrown if a system level exception occurs.
* <b>Note:</b> Application level exceptions should be handled by returning
* an error value, such as <code>Action.ERROR</code>.
*/
public String execute() throws Exception;
}
Action接口提供了一些有用的String常量,这些常量可以用作结果返回值。这些常量可以很方便的作为execute()方法返回的控制字符串的值,因为Struts 2框架内部也使用了这些常量。
通过实现Action接口创建动作组件的Java代码:
public class CreateActionByActionInterface implements Action
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String execute() throws Exception
{
return SUCCESS;
}
}
2. 扩展ActionSupport类实现Action组件
ActionSupport类是实现了Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable这些接口的便利类。它提供了如数据验证、错误消息本地化等功能。作为一个支持类,ActionSupport提供了几个重要接口的默认实现。如果你的Action组件扩展了该类,将会自动获得这些实现的使用。
通过扩展ActionSuppor类创建Action组件Java代码:
public class CreateActionByActionSupport extends ActionSupport
{
private static final long serialVersionUID = -3154931871650405193L;
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
public String execute() throws Exception
{
return SUCCESS;
}
}
向对象传递数据:
如果我们想使用复杂对象而不是JavaBean属性来接收数据,有两种选择实现这样的深层数据转移:第一种也是基于JavaBean;第二中是让Action组件实现ModelDriven接口。
1. 基于JavaBean
一个应用程序对象User:
public class User
{
private String username;
private String password;
private String email;
private String address;
private String telephone;
private int age;
private Date birthday;
public User()
{
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address;
}
public String getTelephone()
{
return telephone;
}
public void setTelephone(String telephone)
{
this.telephone = telephone;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public Date getBirthday()
{
return birthday;
}
public void setBirthday(Date birthday)
{
this.birthday = birthday;
}
public String toString()
{
StringBuilder informationBulider = new StringBuilder();
informationBulider.append("用户信息:\n");
informationBulider.append("【 ");
informationBulider.append("1用户名:").append(this.getUsername()).append(" - ").append("用户密码:").append(
this.getPassword()).append(" - ").append("Email:").append(this.getEmail())
.append(" - ").append("地址:").append(this.getAddress()).append(" - ").append("手机:")
.append(this.getTelephone()).append(" - ").append("年龄:").append(this.getAge()).append(
" - ").append("生日:").append(this.getBirthday());
informationBulider.append(" 】");
return informationBulider.toString();
}
}
使用JavaBean方式将User对象作为Action对象的属性并公开:
public class ObjectAction extends ActionSupport
{
private static final long serialVersionUID = -5733392284874680921L;
private User user;
public User getUser()
{
return user;
}
public void setUser(User user)
{
this.user = user;
}
@Override
public String execute()
{
/*
* 逻辑处理
*/
return SUCCESS;
}
}
使用JavaBean的方式将User的对象作为Action的属性公开,可以让逻辑更加简洁,因为我们让框架处理了User对象的实例化,并且使用来自请求的数据填充了它的属性。不然我们必须自己完成User对象的实例化和数据填充。但是,在使用了这种方法承载数据,在调用具体属性的时候必须使用更深的符号来访问,因为它们必须通过user这个属性才能达到指定的属性。相应的表单界面和结果界面也要在数据引用的地方包含user来反映动作中属性的深度
表单界面的一部分:
<s:textfield name="user.username" label="username"></s:textfield>
结果页面的一部分:
<s:property value="user.username"/>
如你所见,直接使用应用程序的域对象作为一个JavaBean属性,可以让框架做更多的工作。仅有的变化是当我们从JSP页面中引用数据时需要添加一个层次。
2.实现ModelDriven接口的动作
使用ModelDriven动作作为数据承载工具,与使用JavaBean方法不一样的地方是,在视图层数据访问时不需要引入额外的点。ModelDriven动作与使用JavaBean属性公开域数据不同,它通过getModel()方法公开应用程序的域对象。
ModelDriven动作Java代码:
public class ModelDrivenAction extends ActionSupport implements ModelDriven<User>
{
private static final long serialVersionUID = 7600447240288541571L;
private User user = new User();
public User getModel()
{
return user;
}
@Override
public String execute()
{
/*
* doSomething
*/
return SUCCESS;
}
}
实现ModelDriven接口只需要实现getModel()这个方法,它返回模型对象,这里是User对象。在使用ModelDriven的方法时需要自己手动初始化User对象。需要注意的是,ModelDriven动作的execute()方法被调用时,框架已经获得了模型对象的引用,在整个请求过程中框架一直使用它。由于框架从获取方法获得模型的引用,所以如果在动作内部改变了模型数据,框架不会察觉。这可能会导致一些数据不一致的问题:
数据不一致问题的情况之一:
public class ModelDrivenAction extends ActionSupport implements ModelDriven<User>
{
private static final long serialVersionUID = 7600447240288541571L;
private User user = new User();
public User getModel()
{
return user;
}
@Override
public String execute()
{
user = new User();
user.setUsername("new name");
/*
* doSomething
*/
return SUCCESS;
}
}
打开浏览器打开表单输入如下数据:
点击提交,按照代码逻辑应该为username:new name这样的结果,但是真实的结果:
发现我们在execute()方法中设置的修改并没有产生效果。为了避免这种情况,不要创建一个新的实例或者指向已经存在的其他实例的引用。
使用ModelDriven动作承载请求中的数据,可以向使用独立的属性一样传输数据,而不必像对象支持的JavaBean属性方法那样需要显示的说明具体字段的深度。
使用ModelDriven的表单:
<s:form action="model">
<s:textfield name="username" label="username"></s:textfield>
<s:textfield name="password" label="password"></s:textfield>
<s:textfield name="email" label="email"></s:textfield>
<s:textfield name="address" label="address"></s:textfield>
<s:textfield name="telephone" label="telephone"></s:textfield>
<s:textfield name="age" label="age"></s:textfield>
<s:textfield name="birthday" label="birthday"></s:textfield>
<s:submit value="submit"></s:submit>
</s:form>
使用ModelDriven的结果页面:
username:<s:property value="username"/>
3. 域对象用作数据转移的潜在危险
使用域对象用作数据转移有一个潜在的危险需要被指出,当数据自动转移到对象上时就可能会发生问题,如果请求中的参数与域对象属性匹配,那么数据会被移动到这些属性上;若域对象包含一些敏感的数据属性(如ID),你不想把它公开给自动数据机制,一些恶意用户可以在请求中添加一些适当命名的查询字符串,这样这个参数的值会被自动写入到你提供的对象的属性上。
总结:
Action组件有3个作用:(1)封装了框架与模型的交互 (2)作为请求处理过程中的数据转移对象 (3)动作组件负责返回一个控制字符串
创建Action组件有3中方式:(1)非正规方式,只要类提供一个返回一个字符串的execute()方法 (2)通过实现Action接口 (3)通过继承ActionSupport类
在实现Action组件时,有3种方式承载数据:(1)使用JavaBean属性 (2)使用对象支持的JavaBean属性 (3)使用ModelDriven方式