- 首先了解Params 拦截器
作用:Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象
把表单的值赋给栈顶对象的属性 此时栈顶对象即为 Action
在实际的使用中我们想 把 Action 和 Model 清晰地隔离开是有必要的: 有些 Action 类不代表任何Model 对象, 它们的功能仅限于提供显示服务
问题:所以我要在Params 拦截器之前需要把一个Model压入栈顶,以便于在执行到Params 拦截器的时候栈顶对象是个Model 从而对Action中的表单域中对象model属性进行赋值。
这就需要我们使用ModelDriven拦截器
- ModelDriven 拦截器
如果 Action 类实现了 ModelDriven 接口,该拦截器将把 ModelDriven 接口的 getModel() 方法返回的对象置于栈顶
public class EmployeeAction extends ActionSupport implements ModelDriven<Employee>{
private EmployeeService employeeService;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
public String save() {
employeeService.save(employee );
return "save-success" ;
}
private Employee employee;
//该拦截器将把 ModelDriven 接口的 getModel() 方法返回的对象置于栈顶
@Override
public Employee getModel() {
return employee ;
}
}
当用户触发 save请求时, ModelDriven 拦截器将调用 EmployeeAction 对象的 getModel() 方法, 并把返回的模型(Employee实例)压入到 ValueStack 栈.
接下来 Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 因为此时 ValueStack 栈的栈顶元素是刚被压入的模型(Employee)对象, 所以该模型将被填充. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象
查看ModelDriven 底层源代码:
核心代码:
public class ModelDrivenInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
//判断action是否实现 ModelDriven的接口
if (action instanceof ModelDriven) {
ModelDriven modelDriven = (ModelDriven) action;
//获取值栈
ValueStack stack = invocation.getStack();
//在这里调用了action中实现的MOdelDriven方法 返回一个对象
Object model = modelDriven.getModel();
if (model != null) {
//把返回的对象压入栈顶
stack.push(model);
}
if (refreshModelBeforeResult ) {
invocation.addPreResultListener( new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
}
注意:表单元素的name属性要和对应的model的属性名一至
- Preparable 拦截器
Struts 2.0 中的 modelDriven 拦截器负责把 Action 类以外的一个对象压入到值栈栈顶
而 prepare 拦截器负责准备为 getModel() 方法准备 model 但Preparable 拦截器没有在struts-default中这就需要我们使用 paramsPrepareParamsStack 拦截器栈
使用 paramsPrepareParamsStack 拦截器栈
paramsPrepareParamsStack 从字面上理解来说, 这个stack的拦截器调用的顺序为:首先 params,然后 prepare,接下来 modelDriven,最后再 params
Struts 2.0的设计上要求 modelDriven 在 params 之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在 prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。
流程如下:
1. params拦截器首先给action中的相关参数赋值,如id
2. prepare拦截器执行prepare方法,prepare方法中会根据参数,如id,去调用业务逻辑,设置model对象
3. modelDriven拦截器将model对象压入value stack,这里的model对象就是在prepare中创建的
4. params拦截器再将参数赋值给model对象
5. action的业务逻辑执行
实例代码: public class EmployeeAction extends ActionSupport implements Preparable,ModelDriven<Employee>{ 1. params拦截器首先给action中的相关参数赋值,如id private int id; private EmployeeService employeeService; public void setEmployeeService(EmployeeService employeeService) { this.employeeService = employeeService; } //所以当触发save action方法时会在Preparable的拦截器中自动查找action中是否包含 prepareSave()或prepareDoSave() 方法如果包含这个方法就先执行prepareSave() prepareDoSave() public String save() { System. out.println(employee ); employeeService.save(employee ); return "save-success" ; } //通过观察源码执行过程得知 // PrepareInterceptor 拦截器将调用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法 private Employee employee; public void prepareSave(){ //所以在这里进行初始化Model的类 employee= new Employee(); } @Override public Employee getModel() { return employee ; } // 若 Action 实现 Preparable 接口,则 Action 方法需实现 prepare() 方法 //2. prepare拦截器执行prepare方法,prepare方法中会根据参数,如id,去调用业务逻辑,设置model对象 但是一般这个方法都不进行执行具体的方法内容 //PrepareInterceptor 拦截器会根据 alwaysInvokePrepare 属性决定是否执行prepare()方法 @Override public void prepare() throws Exception {} } 源码实现: public class PrepareInterceptor extends MethodFilterInterceptor { PrepareInterceptor 拦截器根据 firstCallPrepareDo 属性决定获取 prepareActionMethodName 、 prepareDoActionMethodName的顺序。默认情况下先获取 prepareActionMethodName (), 如果没有该方法,就寻找prepareDoActionMethodName()。 如果找到对应的方法就调用该方法 private final static String PREPARE_PREFIX = "prepare"; private final static String ALT_PREPARE_PREFIX = "prepareDo"; // 拦截器会根据 alwaysInvokePrepare 属性决定是否执行prepare()方法 private boolean alwaysInvokePrepare = true; private boolean firstCallPrepareDo = false; public void setAlwaysInvokePrepare(String alwaysInvokePrepare) { this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare); } public void setFirstCallPrepareDo(String firstCallPrepareDo) { this.firstCallPrepareDo = Boolean.parseBoolean(firstCallPrepareDo); } @Override public String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); if (action instanceof Preparable) { try { String[] prefixes; // firstCallPrepareDo = false; if (firstCallPrepareDo ) { prefixes = new String[] {ALT_PREPARE_PREFIX , PREPARE_PREFIX}; } else { prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX }; } //会先执行prefixes 数组在PrefixMethodInvocationUtil的invokePrefixMethod 方法中 PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); } catch (InvocationTargetException e) { throw e; } //拦截器会根据 alwaysInvokePrepare 属性决定是否执行prepare()方法 if (alwaysInvokePrepare ) { ((Preparable) action).prepare(); } } return invocation.invoke(); } //执行prefixes 数组在PrefixMethodInvocationUtil类中 默认执行方法 private static final String DEFAULT_INVOCATION_METHODNAME = "execute"; public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) { //得到请求的action类对象 Object action = actionInvocation. getAction(); //得到请求的action类对象请求的方法名 String methodName = actionInvocation.getProxy().getMethod(); //如果方法名称为NUll就赋值默认的execute方法 if (methodName == null) { methodName = DEFAULT_INVOCATION_METHODNAME; } //得到前缀方法 也就是prepareSave()或prepareDoSave() 方法如果包含这个方法就先执 行prepareSave()prepareDoSave() 也就是在请求方action的方法加上prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法 通过getPrefixedMethod 这个方法进行的装配 Method method = getPrefixedMethod (prefixes, methodName, action); //如果包含prepareSave()或prepareDoSave() 方法如果包含这个方法就先执 行prepareSave()prepareDoSave()就进行调用这个对应的方法 if (method != null) { 这里调用 method.invoke(action, new Object[0]); } } public static Method getPrefixedMethod (String[] prefixes, String methodName, Object action) { assert(prefixes != null); String capitalizedMethodName = capitalizeMethodName(methodName); for (String prefixe : prefixes) { //在这里进行配置成 prepareActionMethodName()方法 或 prepareDoActionMethodName String prefixedMethodName = prefixe + capitalizedMethodName; try { return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { } } } return null ; } 以上就是ModelDriven 和 Preparable 拦截器的使用和源码的原理 个人感觉写的比较繁琐了