ModelDriven拦截器
首先要认识到,把Action和Model分隔开是有必要的。有些Action类不代表任何Model对象,他们的功能仅限于提供显示服务。
如果Action实现了
ModelDriven
接口,该拦截器将把ModelDriven
接口的getModel()
方法返回的对象置于栈顶。
实现了ModelDriven后的Action类运行流程(Struts2源码)
- 先执行
ModelDrivenInterceptor
的intercept
方法
public String intercept(ActionInvocation invocation) throws Exception {
//获取Action对象:EmployeeAction对象,此时该Action已经实现了ModelDriven接口
Object action = invocation.getAction();
//判断action是否是ModelDriven的实例
if (action instanceof ModelDriven) {
//强制转换为ModelDriven类型
ModelDriven modelDriven = (ModelDriven)action;
//获取值栈
ValueStack stack = invocation.getStack();
//调用ModelDriven接口的getModel()方法
//即调用EmployeeAction的getModel()方法
Object model = modelDriven.getModel();
if (model != null) {
//把getModel()方法的返回值压入到值栈的栈顶,实际压入的是EmployeeAction的Employee的成员变量
stack.push(model);
}
if (this.refreshModelBeforeResult) {
invocation.addPreResultListener(new ModelDrivenInterceptor.RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
然后执行
ParametersInterceptor
的intercept
方法,该方法会把请求参数的值赋给栈顶对象对应的属性,若栈顶对象没有对应的属性,则查询值栈中下一个对象对应的属性。注意
getModel
方法不能返回匿名对象,虽然返回一个匿名类也可以将其添加到栈顶,但是当前Action类的employee
成员变量却是null
。也就是说成员变量的employee
和返回的匿名对象不是同一个对象。
public Employee getModel(){
return new Employee();
}
paramsPrepareParamsStack拦截器栈
paramsPrepareParamsStack
和paramsPrepareParamsStack
一样都是拦截器栈。而struts-default
包默认使用paramsPrepareParamsStack
拦截器栈。若要使用
paramsPrepareParamsStack
,则需要在struts.xml
文件中配置使用paramsPrepareParams
作为默认的拦截器栈,在struts.xml
文件的package
节点内配置
,配置后则是使用paramsPrepareParamsStack
作为默认的拦截器栈。paramsPrepareParamsStack拦截器栈中的拦截器的调用顺序是先运行
params
,再运行modelDriven
,最后再次运行params
,因此可以先把请求参数赋给Action对应的属性,再根据 赋给Action的那个属性值 决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值
小Demo:进行edit操作,即表单的编辑(更新)操作
Action类的代码:
package struts.crud;
import com.opensymphony.xwork2.ModelDriven;
import org.apache.struts2.interceptor.RequestAware;
import java.util.Map;
/**
* 该代码存在的问题:
* 1.在删除的时候,employeeId肯定不为null,但getModel方法却从数据库中加载了一个对象(多余的步骤)
* 2.执行查询全部信息时,也创建了个Employee对象(浪费)
*
*/
public class EmployeeAction implements RequestAware, ModelDriven {
private Dao dao = new Dao();
private Employee employee;
private Map requests;
//需要在当前的EmployeeAction中定义employeeId属性,用来接收请求参数
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
/**
* 在此代码中:
* 先为EmployeeAction的employeeId赋值(在jsp页面已经传过来了,此处忽略)
* 再根据employeeId从数据库中加载对应的对象放入到值栈中
* 再为栈顶对象的employeeId赋值(实际上此时employeeId已经存在)
* 再把栈顶对象的属性回显到表单中
* @return
*/
public String edit(){
return "edit";
}
public String delete(){
dao.delete(employeeId);
return "success";
}
public String save(){
dao.save(employee);
return "success";
}
public String update(){
dao.update(employee);
return "success";
}
public String list(){
requests.put("emps",dao.getEmployees());
return "list";
}
@Override
public void setRequest(Map < String, Object > map) {
requests = map;
}
@Override
public Employee getModel() {
//employeeId为空,则是新建操作,则要重新new一个Employee对象并返回
if(employeeId == null)
employee = new Employee();
else//若不为空,则是更新操作,则直接从数据库中拿到该对象并返回即可
employee = dao.get(employeeId);
return employee;
}
}
关于表单回显
Struts2表单标签会从值栈中获取对应的属性值进行回显
perpareInterceptor拦截器
上面我们提到的modelDriven
拦截器是负责把Action类以外的一个对象压入到值栈栈顶,而prepare
拦截器负责准备为getModel()
方法准备model
。
若Action实现了
Preparable
接口,则Struts2将尝试执行prepare[ActionMethodName]
方法,若prepare[ActionMethodName]
方法不存在,则尝试执行prepareDo[ActionMethodName]
方法,若都不存在,就都不执行。若
alwaysInvokePrepare
属性为false
,则Struts2将不会调用实现了Preparable
接口的Action的prepare()
方法源码解析
public String doIntercept(ActionInvocation invocation) throws Exception {
//获取Action实例
Object action = invocation.getAction();
//判断Action是否实现了Preparable接口
if (action instanceof Preparable) {
try {
String[] prefixes;
//根据当前拦截器的firstCallPrepareDo属性(默认为false)确定prefixes
if (this.firstCallPrepareDo) {
prefixes = new String[]{"prepareDo", "prepare"};
} else {
prefixes = new String[]{"prepare", "prepareDo"};
}
//若为false,则prefixes:"prepare", "prepareDo"
//调用前缀方法
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
} catch (InvocationTargetException var5) {
Throwable cause = var5.getCause();
if (cause instanceof Exception) {
throw (Exception)cause;
}
if (cause instanceof Error) {
throw (Error)cause;
}
throw var5;
}
//根据当前拦截器的alwaysInvokePrepare(默认是true)决定是否调用Action的prepare方法
if (this.alwaysInvokePrepare) {
((Preparable)action).prepare();
}
}
return invocation.invoke();
}
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
//获取Action实例
Object action = actionInvocation.getAction();
//获取要调用的Action方法的名字,
String methodName = actionInvocation.getProxy().getMethod();
if (methodName == null) {
methodName = "execute";
}
//获取前缀方法
Method method = getPrefixedMethod(prefixes, methodName, action);
//若方法不为null,则通过反射调用前缀方法
if (method != null) {
method.invoke(action);
}
}
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
assert prefixes != null;
//把方法名的首字母变为大写
String capitalizedMethodName = capitalizeMethodName(methodName);
String[] arr$ = prefixes;
int len$ = prefixes.length;
int i$ = 0;
//遍历前缀数组
while(i$ < len$) {
String prefixe = arr$[i$];
//通过拼接的方式,得到前缀方法名,第一次为prepare+[方法名],第二次为prepareDo+[方法名]
String prefixedMethodName = prefixe + capitalizedMethodName;
try {
//利用反射从action中获取对应的方法,若有则返回,并结束循环。
return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException var10) {
LOG.debug("Cannot find method [{}] in action [{}]", prefixedMethodName, action);
++i$;
}
}
return null;
}
所以现在,对于之前paramsPrepareParamsStack
拦截器栈的Demo中存在的问题。可以这么解决:
可以为每一个ActionMethod
准备prepare[ActionMethodName]
方法,而抛弃原来的prepare()
方法,将PrepareInterceptor
的alwaysInvokePrepare
属性置为false
,以避免Struts2框架再调用prepare()
方法。
- 在配置文件中为拦截器栈的属性赋值
在struts.xml
文件中使用如下来设置:
false
重新编写之后的Action类文件如下:
package struts.crud;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
import org.apache.struts2.interceptor.RequestAware;
import java.util.Map;
public class EmployeeAction implements RequestAware, ModelDriven, Preparable {
private Dao dao = new Dao();
private Employee employee;
private Map requests;
//需要在当前的EmployeeAction中定义employeeId属性,用来接收请求参数
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
/**
* 为edit做准备
*/
public void prepareEdit(){
employee = dao.get(employeeId);
}
public String edit(){
return "edit";
}
public String delete(){
dao.delete(employeeId);
return "success";
}
/**
* 为save()做准备
*/
public void prepareSave(){
employee = new Employee();
}
public String save(){
dao.save(employee);
return "success";
}
public void prepareUpdate(){
employee = new Employee();
}
public String update(){
dao.update(employee);
return "success";
}
public String list(){
requests.put("emps",dao.getEmployees());
return "list";
}
@Override
public void setRequest(Map < String, Object > map) {
requests = map;
}
@Override
public Employee getModel() {
return employee;
}
/**
* 作用:为getModel()方法准备model的
* @throws Exception
*/
@Override
public void prepare() throws Exception {
//该方法以及不会被调用了
}
}