zoukankan      html  css  js  c++  java
  • 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 拦截器栈来简化代码

  • 相关阅读:
    小程序自动更新版本
    js深浅拷贝理解
    小程序模仿toast效果
    小程序button默认border
    Java利用POI 读取Excel行列数,坑
    Nginx 极简入门教程
    七、SpringBoot整合持久化层,配置多数据源(SpringBoot系列)
    六、SpringBoot整合aop(SpringBoot系列)
    五、SpringBoot随系统启动任务的方式(SpringBoot系列)
    四、SpringBoot通过CORS解决跨域问题(SpringBoot系列)
  • 原文地址:https://www.cnblogs.com/linhp/p/6265644.html
Copyright © 2011-2022 走看看