struts2 paramsPrepareParamsStack拦截器简化代码(源码分析)

点击查看代码

一、在讲 paramsPrepareParamsStack 之前,先看一个增删改查的例子。

其中,文件结构如下

|-- org.example.preparable
    -- Dao.java
    -- Employee.java
    -- EmployeeAction.java
struts.xml
|-- webapp
emp-edit.jsp
emp-list.jsp
WEB-INF
    -- web.xml

1. Dao.java准备数据和提供增删改查

/**
 * 准备数据,提供增删改查方法
 * 
 * @author baojulin
 *
 */
public class Dao {

    private static Map<Integer, Employee> emps = new LinkedHashMap<Integer, Employee>();

    static {
        emps.put(1001, new Employee(1001, "AA", "aa", "aa@test.com"));
        emps.put(1002, new Employee(1002, "BB", "bb", "bb@test.com"));
        emps.put(1003, new Employee(1003, "CC", "cc", "cc@test.com"));
        emps.put(1004, new Employee(1004, "DD", "dd", "dd@test.com"));
        emps.put(1005, new Employee(1005, "EE", "ee", "ee@test.com"));
    }

    public List<Employee> getEmployees() {
        return new ArrayList<>(emps.values());
    }

    public void delete(Integer empId) {
        emps.remove(empId);
    }

    public void save(Employee emp) {
        long time = System.currentTimeMillis();
        emp.setEmployeeId((int) time);

        emps.put(emp.getEmployeeId(), emp);
    }

    public Employee get(Integer empId) {
        return emps.get(empId);
    }

    public void update(Employee emp) {
        emps.put(emp.getEmployeeId(), emp);
    }

}

2. Employee.java 为model

/**
 * 模型
 * @author baojulin
 *
 */
public class Employee {

    private Integer employeeId;
    private String firstName;
    private String lastName;

    private String email;
    // 省去get set

3. EmployeeAction 控制器

public class EmployeeAction implements RequestAware, ModelDriven<Employee> {

    @Override
    public Employee getModel() {
        employee = new Employee();
        return employee;
    }

    private Dao dao = new Dao();

    private Employee employee;

    public String update() {
        dao.update(employee);
        return "success";
    }

    public String edit() {
        // 编辑,先从数据库获取employee,到页面回显
        Employee emp = dao.get(employee.getEmployeeId());
        // 因为实现了 ModelDriven 接口, 所以,在到达 edit 之前,会通过 getModel 方法,将  employee 放到值栈中的栈顶
        // 所以页面 emp-edit.jsp 可以直接通过栈顶获取 employee
        employee.setFirstName(emp.getFirstName());
        employee.setLastName(emp.getLastName());
        employee.setEmail(emp.getEmail());
        return "edit";
    }

    public String save() {
        dao.save(employee);
        return "success";
    }

    public String delete() {
        dao.delete(employee.getEmployeeId());
        return "success";
    }

    public String list() {
        request.put("emps", dao.getEmployees());
        return "list";
    }

    private Map<String, Object> request;

    @Override
    public void setRequest(Map<String, Object> req) {
        this.request = req;
    }

}

在这里,我们可以先看看 struts 对 ModelDrivenInterceptor 拦截器的实现

 @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();

        // 判断是否实现了 ModelDriven 接口
        if (action instanceof ModelDriven) {
            ModelDriven modelDriven = (ModelDriven) action;
            ValueStack stack = invocation.getStack();
            
            // 通过 getModel 获取 model ,这里是我们的 Employee
            Object model = modelDriven.getModel();
            if (model !=  null) {
                // 放到栈顶
                stack.push(model);
            }
            if (refreshModelBeforeResult) {
                invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
            }
        }
        return invocation.invoke();
    }

通过拦截器的源码,可以看到在edit方法中,employee是在栈顶的。

4. 页面的代码 emp-list.jsp

<body>
    <s:form action="emp-save">
        <s:textfield name="firstName" label="FirstName"></s:textfield>
        <s:textfield name="lastName" label="LastName"></s:textfield>
        <s:textfield name="email" label="Email"></s:textfield>
        <s:submit></s:submit>       
    </s:form>
    <hr>
    <br>
    <table cellpadding="10" cellspacing="0" border="1">
        <thead>
            <tr>
                <td>ID</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>Email</td>
                <td>Edit</td>
                <td>Delete</td>
            </tr>
        </thead>
        <tbody>
            <s:iterator value="#request.emps">
                <tr>
                    <td>${employeeId }</td>
                    <td>${firstName }</td>
                    <td>${lastName }</td>
                    <td>${email }</td>
                    <td><a href="emp-edit?employeeId=${employeeId }">Edit</a></td>
                    <td><a href="emp-delete?employeeId=${employeeId }">Delete</a></td>
                </tr>
            </s:iterator>
        </tbody>
    </table>
</body>

5. emp-edit.jsp页面

<body>
    
    <s:debug></s:debug>
    
    <br>
    <br>
    
    <s:form action="emp-update">
        
        <s:hidden name="employeeId"></s:hidden>
        
        <s:textfield name="firstName" label="FirstName"></s:textfield>
        <s:textfield name="lastName" label="LastName"></s:textfield>
        <s:textfield name="email" label="Email"></s:textfield>
        
        <s:submit></s:submit>       
    </s:form>
    
</body>

6 简化上面代码

上面的代码中,不够简洁,我们可以使用struts2的 paramsPrepareParamsStack 拦截器来简化代码

二、struts2的 paramsPrepareParamsStack 拦截器

paramsPrepareParamsStack 拦截器跟 defaultStack 拦截器一样,都是struts拦截器。

关于 struts2 的拦截器栈,可以在struts2-core 的jar包中的 struts-default.xml 配置文件中查看。

1. defaultStack拦截器栈

其中,defaultStack 拦截器栈如下:

<interceptor-stack name="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\..*,^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-ref name="debugging"/>
</interceptor-stack>

这里可以看到,prepare 拦截器,modelDriven 拦截器,和 params 拦截器的执行顺序如下:

prepare --> modelDriven --> params

2. paramsPrepareParamsStack 拦截器栈

<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 --> prepare --> modelDriven --> params

3. 使用 paramsPrepareParamsStack 拦截器栈 的步骤

3.1 实现 Preparable

里面只有一个方法

// 此方法不一定会被调用
// 通过 prepare.alwaysInvokePrepare 参数指定(后面讲)
@Override
public void prepare() throws Exception {
    System.out.println("prepare()...");
}

3.2 为 save update edit 方法都增加一个以 prepare 开头的方法(固定写法)

如 : prepareSave,prepareUpdate,prepareEdit

当我们访问save的时候,会先默认访问 prepareSave 方法
同样,访问 update 会先访问 prepareUpdate 方法。

这样,我们就可以在调用save之前创建 employee 对象了,而不用在 getModel 中创建,在getModel 中,每次请求这个action,都会创建一个 employee,浪费资源

public void prepareSave(){
    employee = new Employee();
}

在访问edit 的时候,可以先从数据库获取employee

public void prepareEdit(){
    System.out.println("prepareEdit()");
    employee = dao.get(employeeId);
}

update 也一样

public void prepareUpdate(){
    System.out.println("prepareUpdate()");
    employee = new Employee();
}

通过这样的方式,为特殊需要获取employee对象定制一个私有的方法,减少资源浪费

3.3 struts.xml 配置 paramsPrepareParamsStack 拦截器栈

<!-- 配置使用 paramsPrepareParamsStack 作为默认的拦截器栈 -->
<!-- 修改 PrepareInterceptor 拦截器的 alwaysInvokePrepare 属性值为 false , 
为 false 时,action中的 prepare 方法不掉用 -->
<interceptors>
    <interceptor-stack name="atguigustack">
        <interceptor-ref name="paramsPrepareParamsStack">
            <param name="prepare.alwaysInvokePrepare">false</param>
        </interceptor-ref>
    </interceptor-stack>
</interceptors>

3.4 修改后的action

修改后的action中可以看出,代码简洁了不少

public class EmployeeAction implements RequestAware, ModelDriven<Employee>, Preparable{
    
    private Dao dao = new Dao();
    
    private Employee employee;
    
    public String update(){
        dao.update(employee);
        return "success";
    }
    
    public void prepareUpdate(){
        System.out.println("prepareUpdate()");
        employee = new Employee();
    }

    public String edit(){   
        return "edit";
    }
    
    public void prepareEdit(){
        System.out.println("prepareEdit()");
        employee = dao.get(employeeId);
    }
    
    public String save(){
        dao.save(employee);
        return "success";
    }
    
    public void prepareSave(){
        employee = new Employee();
    }
    
    public String delete(){
        dao.delete(employeeId);
        return "success";
    }
    
    public String list(){
        request.put("emps", dao.getEmployees());
        return "list";
    }
    
    private Map<String, Object> request;

    @Override
    public void setRequest(Map<String, Object> arg0) {
        this.request = arg0;
    }
    
    private Integer employeeId;
    
    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }
    
    @Override
    public Employee getModel() {
        return employee;
    }

    @Override
    public void prepare() throws Exception {
        System.out.println("prepare...");
    }
    
}

4. 源码分析 paramsPrepareParamsStack

在 PrepareInterceptor 拦截器中,代码如下。

    @Override
    public String doIntercept(ActionInvocation invocation) throws Exception {
        //获取action,这里是EmployeeAction
        Object action = invocation.getAction();

        // 判断是否实现 Preparable 借口
        if (action instanceof Preparable) {
            try {
                String[] prefixes;
                // firstCallPrepareDo 默认是 false
                if (firstCallPrepareDo) {
                    prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
                } else {
                    // prefixes 内容是:[prepare, prepareDo]
                    prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
                }
                PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
            }
            catch (InvocationTargetException e) {
                省略...
            }

            // 这里是调用 Preparable 接口中的 prepare 方法 , 需要 alwaysInvokePrepare 为 true 
            // 我们在配置文件中,配置了 false ,所以不会执行
            if (alwaysInvokePrepare) {
                ((Preparable) action).prepare();
            }
        }

        return invocation.invoke();
    }

PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); 如下,主要是去获取prepareEdit 类似的方法,如果存在,则执行

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;
    }
    
    // 获取 prepareEdit 类似的方法
    Method method = getPrefixedMethod(prefixes, methodName, action);
    if (method != null) {
        method.invoke(action, new Object[0]);
    }
}

getPrefixedMethod(prefixes, methodName, action); 如下,通过for循环获取刚刚数组 [prepare,prepareDo] ,然后拼接上刚刚的方法名,然后反射调用

public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
    assert(prefixes != null);
    String capitalizedMethodName = capitalizeMethodName(methodName);
    for (String prefixe : prefixes) {
        // 这里获取 prepareEdit 或 prepareDoEdit ,然后返回
        String prefixedMethodName = prefixe + capitalizedMethodName;
        try {
            return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
        }
        catch (NoSuchMethodException e) {
            ...
        }
    }
    return null;
}

看到这里,已经知道了为什么会执行prepareEdit 方法了。
其他的prepareUpate 一样。
这样就实现了 paramsPrepareParamsStack 拦截器栈来简化代码

转载于:https://www.cnblogs.com/linhp/p/6265644.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值