Prepareble拦截器到底是做什么用的
Struts2.0中的ModelDriven拦截器的getModel()方法负责把要执行的Action类以外的的一个对象放到ValueStack值栈的栈顶。
prepare拦截器就是负责准备为ModelDrven拦截器的getModel()方法准备model。
该拦截器可以在执行当前Action的目标方法前进行一些准备工作来处理一些业务逻辑。
看一下拦截器的执行过程
<interceptor-stack name="paramsPrepareParamsStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param>
</interceptor-ref>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</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-stack>
我这里使用的是params拦截器栈,首先执行params拦截器,接着执行prepare拦截器,在执行ModelDriven拦截器,在执行params拦截器
public String doIntercept(ActionInvocation invocation) throws Exception {
// 获取实例
Object action = invocation.getAction();
// 判断Action是否实现了Preparable接口
if (action instanceof Preparable) {
try {
String[] prefixes;
// 根据当前拦截器firstCallPrepareDo(默认为false)
// 属性确定prefixes
if (firstCallPrepareDo) {
prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
} else {
prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
}
// 若为false,则prefixes为:prepare、prepareDo
// 调用前缀方法
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Exception) {
throw (Exception) cause;
} else if(cause instanceof Error) {
throw (Error) cause;
} else {
throw e;
}
}
// 根据当前拦截器的alwaysInvokePrepare(默认为true)决定是否调用Action的prepare方法
if (alwaysInvokePrepare) {
((Preparable) action).prepare();
}
}
return invocation.invoke();
}
}
我们来看一下执行流程:
- 首先获取了当前Action的实例,紧接着判断了当前Action是否实现了Prepareble接口。
- 根据bool值得到一个数组确认了前缀数组(["prepare","prepareDo"]的一个字符串数组);
- 然后执行前缀方法(该方法可能是空,后面的源代码中大家会看得到),进入方法后的代码 如下:
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
Object action = actionInvocation.getAction();
String methodName = actionInvocation.getProxy().getMethod();
if (methodName == null) {
// if null returns (possible according to the docs), use the default execute
methodName = DEFAULT_INVOCATION_METHODNAME;
}
Method method = getPrefixedMethod(prefixes, methodName, action);
if (method != null) {
method.invoke(action, new Object[0]);
}
}
- 获取了当前action,和当前的action方法
- 判断当前methodName是不是为空
- 然后获取了一个前缀方法,getPrefixedMethod
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
assert(prefixes != null);
String capitalizedMethodName = capitalizeMethodName(methodName);
for (String prefixe : prefixes) {
String prefixedMethodName = prefixe + capitalizedMethodName;
try {
return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
}
catch (NoSuchMethodException e) {
// hmm -- OK, try next prefix
if (LOG.isDebugEnabled()) {
LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString());
}
}
}
return null;
}
- 第3行,让目标方法首字母编程了大写(比如update,转换后成Update)
- 紧接着下一行遍历前缀数组,把前缀拼接在目标方法前面(prepareUpdate,prepareDoUpdate)
- 然后7行,尝试着从当前Action中获取拼接好的方法,如果有就返回,如果没有会生成一个NoSuchMethodException的异常
- 根据数组前缀的生成方式,去执行这两个方法(只不过是生成的顺序不一样,控制就是在前面的那个bool值)。
- 再接回刚才的getPrefixedMethod方法,返回后判断这个拼接好的方法是否为空,然后接着执行method.invoke方法(如果两个方法都是空的话, 就都不执行)
- 回到刚才的doIntercept方法,下面还有一个alwaysInvokePrepare控制的方法
- 若alwaysInvokePrepare为false,责不会去执行实现了Prepareble接口的Action的prepare方法;
package com.yu.struts2.app;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
public class EmployeeAction implements RequestAware ,ModelDriven<Employee>, Preparable{
private Dao dao = new Dao();
public String list(){
requestMap.put("emps", dao.getEmployees());
return "list";
}
public String delete(){
dao.delete(employeeId);
return "success";
}
private Employee employee;
public String save(){
// 1.获取请求参数:通过定义对应属性的方式
// 2.调用Dao的save方法
dao.save(employee);
return "success";
}
public void prepareSave(){
employee = new Employee();
}
public String edit(){
// 1.获取传入的employeeId:employee.getEmployeeId()
// 2.根据employeeId获取Employee对象
//Employee emp = dao.get(employee.getEmployeeId());
// 3.把栈顶对象的属性转配好,此时栈顶对象时employee
// 目前的employee对象只有employeeId属性,其他属性为null
/*
* Struts2表单回显时:从值栈栈顶开始查找匹配的属性,若找到就添加到value属性中
*/
// employee.setEmail(emp.getEmail());
// employee.setFirstName(emp.getFirstName());
// employee.setLastName(emp.getLastName());
// 不能够进行表单的回显,因为经过重写赋值的employee对象不再是栈顶对象
//employee = dao.get(employee.getEmployeeId());
// 手动的把从数据库中获取的Employee对象放到值栈的栈顶。
// 但此时值栈栈顶及第二个对象均为Employee对象,不够完美
//ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
return "edit";
}
public void prepareEdit(){
employee = dao.get(employeeId);
}
public String update(){
dao.update(employee);
return "success";
}
public void prepareUpdate(){
employee = new Employee();
}
private Map<String, Object> requestMap;
@Override
public void setRequest(Map<String, Object> arg0) {
// TODO Auto-generated method stub
this.requestMap = arg0;
}
private Integer employeeId;
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
@Override
public Employee getModel() {
// 判断是Create还是Edit。
// 若为Create,则employee = new Employee();
// 若为Edit,则employee = dao.get(employeeId);
// 判定标准为是否有employeeId这个参数。若有该参数,则视为Edit;若没有该参数,则视为Create
// 若通过employeeId来判断,则需要在modelDriven拦截器之前执行一个params拦截器!
// 而这可以通过使用paramsPrepareParams拦截器栈实现
// 需要在struts.xml文件中配置使用paramsPrepareParams作为默认的拦截器栈
// if(employeeId == null){
// employee = new Employee();
// }
// else
// employee = dao.get(employeeId);
//
return employee;
}
/*
* prepare方法的主要作用:为getModel()方法准备model的
*/
@Override
public void prepare() throws Exception {
System.out.println("prepare...");
}
}
可以为每一个ActionMethod准备一个prepareXxxx方法,来替换掉实现了Prepareble接口的prepare方法;
将
alwaysInvokePrepare设置为false,以避免struts框架再次调用prepare方法;
<interceptors>
<interceptor-stack name="mycustomStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>