调用非execute方法
在前面有关Action的学习中,我们的Action中真正实现业务逻辑的只有execute方法,如果我们每个Action中都只有这么一个方法的话,那么当我们程序中需要的功能很多时,我们就不得不手动编写很多的Action类了,这显然是不合理的。前面我也说道了我们的Action类并不一定非得继承某个类或者实现某个接口,我们可以使用POJO来作为我们的Action,并且我们的Action中也并不一定非得要有execute方法,如果我们使用的不是execute方法,那么我们就需要在配置Action的是时候在action标签上使用method属性来指出我们需要使用的动作方法。
那么我们也可以在一个Action中编写多个用于实现业务逻辑的方法,他们分别执行不同的功能,但是做的工作又是有相似的地方。比如我们可以将所有与用户相关的处理操作都写在UserAction当中,那么这样我们就能够更好的组织我们的代码。同样,我们只需要在struts.xml中为我们的action标签指定我们要使用的method即可。
要实现在一个Action类中调用非execute方法有三种实现方式:
(1)使用method属性
package action; import bean.User; import com.opensymphony.xwork2.ActionSupport; public class UserAction extends ActionSupport{ private String userName; private String password; 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; } @Override public String execute() throws Exception { //仅用于显示信息输入页面 return INPUT; } //保存User public String save(){ User user = new User(); user.setUserName(userName); user.setPassword(password); //将user的信息保存到数据库 //UserDao.save(user) return SUCCESS; } }
这里我们还需要一个User实体类,其实就是一个简单的JavaBean即可。
package bean; public class User { private String userName; private String password; 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; } }
然后在struts.xml中对我们的Action进行配置
<package name="default" namespace="/" extends="struts-default">
<action name="userAdd" class="action.UserAction">
<resultname="input">/input.jsp</result>
</action>
<action name="userSave" class="action.UserAction" method="save">
<result name="success">/success.jsp</result>
</action>
</package>
Input.jsp
<form action="userSave.action" method="post">
username : <input type="text" name="userName"/><br/>
password :<input type="password" name="password"><br/>
<input type="submit" value="submit"/>
</form>
success.jsp
<body>
hello ${userName }<br/>
your password is ${password }
</body>
我们在浏览器中测试一下:
我们直接在浏览器中访问userAdd.action,可以看到显示了信息输入页面
提交之后正确的显示除了success结果页面,说明我们的配置没有问题。
(2)使用通配符
除了使用上面说到的方法外,我们也可以不用在struts.xml中对Action中的每一个动作方法都进行配置,我们可以之配置一个,在这个actin中使用通配符来指定将要执行哪个方法。
<package name="default" namespace="/" extends="struts-default">
<action name="*User" class="action.UserAction" method="{1}">
<result name="input">/input.jsp</result>
<result name="success">success.jsp</result>
</action>
</package>
在UserAction中增加一个add方法,直接返回”input”,将input.jsp中form的action属性修改为”saveUser.action”。我们在浏览器中测试一下:
直接访问addUser.action,浏览器将会显示信息输入页面,说明调用了UserAction中的add方法:
页面提交到saveUser.action,调用了UserAction的save方法并呈现success结果页面.
注:通配符的方式也可以应用在result标签上。
(3)动态方法调用
除了上面介绍的方法,struts2中还有一种实现方法——动态方法调用。使用动态方法调用的格式为 action!method即使用”!”来连接我们配置的action和要执行的方法,使用这种方式我们不需要为acttion标签指定method属性。
<package name="default" namespace="/" extends="struts-default">
<action name="user" class="action.UserAction">
<result name="input">/input.jsp</result>
<result name="success">success.jsp</result>
</action>
</package>
然后我们要对input.jsp做一些修改,将form的action属性修改为”user!save”:
<form action="user!save" method="post">
username : <input ype="text" name="userName"/><br/>
password :<input type="password" name="password"><br/>
<input type="submit" value="submit"/>
</form>
修改完成之后我们在浏览器中直接访问http://localhost/action/user!add
Input.jsp页面被呈现,说明UserAction的add方法被调用。
提交到user!save后success.jsp被呈现,说明UserAction的save方法被调用。
注意:对于使用通配符的方法来说,Struts2会认为我们将这些匹配到的方法就像是硬编码在struts.xml中一样,因此我们可以为这些进行匹配的action做一些其他处理,比如数据验证、本地消息和类型转换等。但是使用动态方法调用的方式,struts2知道我们调用了同一个action,只是执行了非execute方法,因此使用这种方式,这些动作方法都会共享一个Action的配置。所以从这个角度上来说,通配符的方式优于动态方法调用的方式。我们可以在struts.xml中通过配置来决定是否启用动态方法调用。
<constant name="struts.enable.DynamicMethodInvocation"value="false"/>
向对象传递数据
上面我们接触到的Action类都有一个共同的特性,那就是在action中使用属性来接收请求中的参数。我们定义了一个User类,专门由于表示User实体,但是在我们的Action类中我们都是手动的new一个User实体对象,然后使用set方法将接收到的值设置到User实体对象上,那么能不能直接使用User对象类接收请求中的参数,我们在动作方法中就可以直接操作User实体对象呢,答案是肯定的。实现的方式有两种。
(1) 对象支持的javabean属性
我们已经知道Strtus2中的params拦截器会自动将请求中的数据转移到动作对象属性上。那么我们也可以直接使用实体对象作为动作对象的属性,这样Struts2就能自动将请求中的数据填充到我们的实体对象上。为了实现这个功能,我们需要在jsp页面中多做一些工作:
public class UserAction extends ActionSupport{ private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String add() throws Exception { //仅用于显示信息输入页面 return INPUT; } //保存User public String save(){ //将user的信息保存到数据库 //UserDao.save(user) return SUCCESS; } }
input.jsp
<form action="user!save" method="post">
username : <input type="text" name="user.userName"/><br/>
password :<input type="password" name="user.password"><br/>
<input type="submit" value="submit"/>
</form>
Success.jsp
<body>
hello ${user.userName }<br/>
your password is ${user.userName }
</body>
运行浏览器进行测试,其结果和前面的测试相同,页面均能正常执行。
需要注意的是,我们的User对象并不需要我们手动实例化,Strtus2会自动实例化user,并且装配上面的属性。要实现这个功能,我们需要在结果视图中明确指出Action类中的实体(域)对象。下面学习的第二种方法我们的jsp页面可以不做任何改变,就和全部使用javabean一样。
(1) ModelDriven动作
要使用ModelDriven动作需要我们的动作实现com.opensymphony.xwork2.ModelDriven接口,在该接口中定义了一个getModel方法,我们要在Action类中实现这个方法,并在其中将我们的域对象返回。该接口支持泛型。需要注意的就是我们在返回域对象时要确保这个域对象已经实例化了。
public class UserAction extends ActionSupport implements ModelDriven<User> { private User user = new User(); public String add() throws Exception { // 仅用于显示信息输入页面 return INPUT; } public User getModel() { return user; } // 保存User public String save() { // 将user的信息保存到数据库 // UserDao.save(user) return SUCCESS; } }
要做的改变就是这些,结果视图里面和最初使用纯JavaBean实现时相同。
运行浏览器进行测试,可以看到得出的结果和前面相同。其实现原理是通过拦截器来调用,查看defaultStack拦截器栈,会看到其中有一个ModelDriven拦截器:
<interceptor-stack ame="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="debugging"/>
</interceptor-stack>
这个拦截器在params拦截器之前,那么会在参数传递到到Action对象上之前将通过getModel获取到的域对象压入valueStack中,以供params拦截器将参数设置上去。查看文档可以知道modeldriven拦截器的实现类是com.opensymphony.xwork2.interceptor. ModelDrivenInterceptor
查看该拦截器的interceptor方法源码:
public Stringintercept(ActionInvocation invocation)throws Exception {
Object action = invocation.getAction();
if (actioninstanceof ModelDriven) {
ModelDriven modelDriven =(ModelDriven) action;
ValueStack stack =invocation.getStack();
Object model =modelDriven.getModel();
if (model != null) {
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven,model));
}
}
return invocation.invoke();
}
可以清晰看出这个拦截器对域对象所做的处理。从这里也可以看出,我们的域对象必须先实例化,才能产生效果。
总结:虽然使用实体对象保存请求数据看起来比较不错,不过在实际使用中还是直接使用javabean属性接收数据的方式比较多,这种方式操作简单,也比较容易控制。