1. 问题

  • Struts2 的 Action 我们将它定义为一个控制器,但是由于在 Action 中也可以来编写一些业务逻辑,也有人会在 Action 输入业务逻辑层。
  • 但是在企业开发中,我们一般会将业务逻辑层单独编写,而不是将它与 action 层写到一起。
  • 之前的练习中,我们一直将属性如 username 、 password 等保存在了 action 中。
  • 这样做了以后导致我们在调用业务逻辑层时可能需要将Action的对象传过去。
  • 但是这样做无异于直接将 Servlet 传给 Service,导致两层之间的耦合,而且我们也不希望其他对象可以直接使用Action对象。
  • 要解决这个问题,我们可以采用的一种方式是,专门在Action中定义一个属性,用来封装信息。然后只需要将这个对象传个service即可。
  • 但是这样又带来了一个新问题,谁来将属性封装进对象呢?答案就是ModelDriven拦截器
  • 拦截器:
    • 拦截器的作用和过滤器类似。
    • 拦截器可以在请求到达Action之前进行拦截,希望在请求到达Action之前通过拦截器做一些准备工作。

  • Struts2 简单的运行流程:

    • 请求首先到达StrutsPrepareAndExecuteFilter.doFilter()
    • 在doFilter方法中,先获取ActionMapping
      • 判断:如果ActionMapping为null,不是Struts请求,直接放行
      • 如果ActionMapping不为null,是Struts请求,继续处理
    • 通过configurationManager加载Struts的配置信息,找到请求对应的Action对象
      • 根据配置信息创建ActionProxy代理类。
    • StrutsActionProxy.execute()方法中调用DefaultActionInvocation.invoke()方法
    • 对所有的拦截器进行迭代在去分别调用拦截器intercept方法,进行拦截请求
    • intercept方法我们对请求进行一些处理,处理完毕以后继续DefaultActionInvocation.invoke()方法
    • 如此反复直到所有的拦截器都被调用
    • 最后才去执行Action的方法。


2. 请求参数在哪封装的:

  • 请求参数在到达Action之前,会先经过ParametersInterceptor拦截器,
  • 在该拦截器中会自动对将请求参数封装进值栈的栈顶对象,
  • 他会根据属性名将属性值设置进栈顶对象的相应属性中,
  • 如果栈顶中没有该属性,则继续向第二对象进行封装。
<!-- 查看 ParametersInterceptor 源码-->
@Override
public String doIntercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();
if (!(action instanceof NoParameters)) {
ActionContext ac = invocation.getInvocationContext();
final Map<String, Object> parameters = retrieveParameters(ac); if (LOG.isDebugEnabled()) {
LOG.debug("Setting params " + getParameterLogMap(parameters));
} if (parameters != null) {
Map<String, Object> contextMap = ac.getContextMap();
try {
ReflectionContextState.setCreatingNullObjects(contextMap, true);
ReflectionContextState.setDenyMethodExecution(contextMap, true);
ReflectionContextState.setReportingConversionErrors(contextMap, true); ValueStack stack = ac.getValueStack();
setParameters(action, stack, parameters); // 在这方法中设置参数到对象中
} finally {
ReflectionContextState.setCreatingNullObjects(contextMap, false);
ReflectionContextState.setDenyMethodExecution(contextMap, false);
ReflectionContextState.setReportingConversionErrors(contextMap, false);
}
}
}
return invocation.invoke();
}

3. ModelDriven拦截器

  • 虽然我们知道了参数是从什么地方压入栈顶对象的,但是我们还是无法更好的处理方式把参数封装成对象,放到对象中去。
  • 所以我们需要使用ModelDriven 拦截器
  • 我们查看 ModelDrivenInterceptor 源码能发现,在该拦截器中,对于实现了 ModelDriven 接口的Action ,会调用getModel() 方法把,getModel() 返回的对象压入值栈栈顶,所以对于我们来说,可以使用一个JavaBean 作为参数的分装
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction(); if (action instanceof ModelDriven) {
ModelDriven modelDriven = (ModelDriven) action;
ValueStack stack = invocation.getStack();
Object model = modelDriven.getModel();
if (model != null) {
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
return invocation.invoke();
}
  • 当用户触发 add 请求时, ModelDriven 拦截器将调用 EmployeeAction 对象的 getModel() 方法, 并把返回的模型(Employee实例)压入到 ValueStack 栈.
  • 接下来 Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 因为此时 ValueStack 栈的栈顶元素是刚被压入的模型(Employee)对象, 所以该模型将被填充. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象
/**
* 实现 ModelDriven 接口
*/
public class EmployeeAction extends ActionSupport implements RequestAware, ModelDriven<Employee> //================= // 重写实现 getModel 方法
@Override
public Employee getModel() { System.out.println(" getModel() Method, ............"); if (employee == null) {
employee = new Employee();
}
// 如果通过 return new Employee() 的方式,虽然栈顶对象是Employee,但是在Action 中的Employee为Null
return employee;
}
  • 但是我们还有一个问题需要解决, 我们去修改表单数据的时候。通常需要使用Id 去数据库中查询出来对象。如果我们直接用下面的方式,在修改页面是无法获取到对应的值。

    • 示例中我们只是使得 employee 引用复制了一份,并且指向了给 employeeDao.getEmpById(employee.getId());对象,实际上我们在栈顶兑现给的值并没有改变
public String input() {
// 因为点击修改的时候,会put Id值到栈顶对象Employee 中,所以我们查询出来的employee 并不会放到栈顶
// 如果这样的话 我们把指向 ValueStack 栈顶的引用,改为了从数据库查询出来的值。
// 所以 ValueStack 中的 employee 并没有被赋值
employee = employeeDao.getEmpById(employee.getId()); System.out.println(employee); return "input";
}
  • 但是我们如果一个个的去赋值属性又特别麻烦.。如果使用获取ValueStack 的方式去重新放入栈顶再弹出的方式也不是一个好的方法

Employee emp = employeeDao.getEmpById(employee.getId());
employee.setName(emp.getName());
employee.setRole(emp.getRole());
employee.setDept(emp.getDept());
employee.setAge(emp.getAge());
  • 所以我们最好的方式,是在 getModel() 方法中,根据 Action 的属性 id 去数据库中获取资源。但是程序中默认使用的是 defaultStack 拦截器栈。在defaultStack拦截器栈中,modelDriven 拦截器之前并没有 params 拦截器,如果需要对Action 参数赋值的话,就需要在 modelDriven 拦截器之前设置params 拦截器
 <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="datetime"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
<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-ref name="deprecation"/>
</interceptor-stack>
  • 根据上面的结果,我们需要做的操作是:

    1. 先调用 param 拦截器封装一部分属性进入Action
    2. 再调用modelDriven 拦截器进入值栈
    3. 在 getModel() 方法中判断id 是否为空,然后再返回不同的结果
  • 于是,我们可以使用 paramsPrepareParamsStack 拦截器栈,paramsPrepareParamsStack 这个拦截器栈中params拦截器会执行两次,
    • 一次在ModelDriven之前,一次在ModelDriven之后,
    • 这样我们就可以根据不同的请求参数,给值栈栈顶放入不同对象。
  • 在 struts.xml 修改默认的拦截器栈
<!-- 修改拦截器栈 -->
<interceptors>
<interceptor-stack name="myInterceptorStck" >
<interceptor-ref name="paramsPrepareParamsStack"></interceptor-ref>
</interceptor-stack>
</interceptors> <!-- 设置使用的拦截器为 我们设置的拦截器栈: myInterceptorStck -->
<default-interceptor-ref name="myInterceptorStck"></default-interceptor-ref>
  • 使用paramsPrepareParamsStack 拦截器栈后,修改后的 getModel()input 方法
@Override
public Employee getModel() { System.out.println(" getModel() Method ............"); if (id == null) {
employee = new Employee();
} else {
employee = employeeDao.getEmpById(id);
}
return employee;
} // ====================
public String input() {
return "input";
}
  • 这样使得代码就更加的简洁了一些

4. 新的问题

  • 在使用 paramsPrepareParamsStack 拦截器栈 之后,虽然已经很简洁了,但是我们出现了新的问题。

    • 在我们做 CRUD 操作是, deleteupdate 方法都带有 id

      • 一个根据 id 删除用户
      • 一个是更新用户信息
    • 都有 id 就会导致调用getModel会去数据库中查询员工信息
    • 但是实际业务上我们并没有这样的需求
    • 所以我们引出了 Preparable拦截器

5. Preparable 拦截器

  • Struts 2.0 中的 modelDriven 拦截器负责把 Action 类以外的一个对象压入到值栈栈顶
  • 而 prepare 拦截器负责准备为 getModel() 方法准备 model, 并且在 modelDriven 拦截器之前
  • PrepareInterceptor拦截器使用方法
    • 若 Action 实现 Preparable 接口,则 Action 方法需实现 prepare() 方法
    • PrepareInterceptor 拦截器将调用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法
    • PrepareInterceptor 拦截器根据 firstCallPrepareDo 属性决定获取 prepareActionMethodName 、prepareDoActionMethodName 的顺序。默认情况下先获取 prepareActionMethodName (), 如果没有该方法,就寻找prepareDoActionMethodName()。如果找到对应的方法就调用该方法
    • PrepareInterceptor 拦截器会根据 alwaysInvokePrepare 属性决定是否执行 prepare() 方法
// com.opensymphony.xwork2.interceptor.PrepareInterceptor
private final static String PREPARE_PREFIX = "prepare";
private final static String ALT_PREPARE_PREFIX = "prepareDo"; private boolean alwaysInvokePrepare = true;
private boolean firstCallPrepareDo = false;
//===============
@Override
public String doIntercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction(); if (action instanceof Preparable) {
try {
String[] prefixes;
//根据 firstCallPrepareDo 的值(默认为false)来决定调用两个prepare 方法,可以在struts.xml 中修改
if (firstCallPrepareDo) {
prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
} else {
prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
}
//调用
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.prepareXxx方法
    • action.prepare方法
    • action.getModel
    • action.action方法
  • 我们可以在prepareXxx方法做一个个性化的处理,可以在prepare方法做一些统一的处理
public String add() {
employeeDao.save(employee);
return SUCCESS;
}
/**
* 该方法会在add方法执行之前执行
* 也会在getModel方法执行之前执行,放到值栈栈顶中
*/
public void prepareDoAdd() {
System.out.println("prepareDoAdd............");
employee = new Employee();
} //======================================================
public String del() {
employeeDao.delete(employee.getId());
return SUCCESS;
}
//======================================================
public String input() {
return "input";
}
public void prepareInput() {
System.out.println("prepareInput.........");
employee = employeeDao.getEmpById(id);
}
//======================================================
public String update() {
employeeDao.update(employee);
return SUCCESS;
}
/**
* 在update方法之前,初始化employ,然后由 getModel 方法放到栈顶
*/
public void prepareUpdate() {
System.out.println("prepareUpdate..............");
employee = new Employee();
}
//======================================================
@Override
public Employee getModel() {
System.out.println("getModel。。。。。。。。。。。");
return employee;
}
//======================================================
/**
* 该方法中,可以做一些统一的处理
*/
@Override
public void prepare() throws Exception { System.out.println("prepare.................."); /*
* 例如:对于 delete 方法,虽然该方法只需要使用 id,
* 但是我们调用该方法之前,调用 getModel() 返回的是个 null
* 所以我们可以在这 做一次判断
*/
if (employee == null) {
employee = new Employee();
}
}
  • 对于我们 如果想修改 PrepareInterceptor拦截器 中的一些参数。

    • 修改 prepareXxx 和 prepareDoXxxx 调用的顺序
    • 修改 alwaysInvokePrepare 的值,使得 Action 不调用 prepare() 方法
    • 参考 docs 中 Interceptor Parameter Overriding
<!-- 修改拦截器栈 -->
<interceptors>
<interceptor-stack name="myInterceptorStck" >
<interceptor-ref name="paramsPrepareParamsStack">
<!-- 修改拦截器栈属性值,name.filed -->
<param name="prepare.alwaysInvokePrepare">false</param>
<param name="prepare.firstCallPrepareDo">true</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>

6. ModelDriven拦截器、Preparable 拦截器的更多相关文章

  1. struts2 ModelDriven 和 Preparable 拦截器

    Struts2 运行流程图-1

  2. ModelDriven 和 Preparable 拦截器

    Params 拦截器 Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueSta ...

  3. Struts2 - ModelDriven 拦截器、Preparable 拦截器

    开篇:拦截器在Struts中的作用 在我们的web.xml中,我们配置了一个过滤器,实现将所有请求交付StrutsPrepareAndExecuteFilter类.一旦接受到任意action的请求,该 ...

  4. Struts2-整理笔记(五)拦截器、拦截器配置

    拦截器(Interceptor) 拦截器是Struts2最强大的特性之一,它是一种可以让用户在Action执行之前和Result执行之后进行一些功能处理的机制. 拦截器的优点 简化了Action的实现 ...

  5. SpringMCVC拦截器不拦截静态资源

    SpringMCVC拦截器不拦截静态资源 SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下 ...

  6. spring拦截器不拦截方法名原因

    开发一个基于注解的登录拦截器,遇到拦截器只能拦截controller不能拦截到具体的方法名,这样拦截器就完全没用,经过仔细摸索,DefaultAnnotationHandlerMapping和Anno ...

  7. Spring boot自定义拦截器和拦截器重定向配置简单介绍

    大家好: 本文简单介绍一下用于权限控制的Spring boot拦截器配置,拦截器重定向问题. 开发工具:jdk1.8   idea2017(付费版,网上找的破解教程) 1,首先使用idea创建一个Sp ...

  8. springboot拦截器的拦截配置和添加多个拦截器

    在spring2.0之前的版本大部分都采用extends WebMvcConfigurerAdapter,把拦截器配置成一个bean,具体的方法,我不细说,网上一大堆.而在spring2.0之后,这个 ...

  9. SpringBoot系列(十一)拦截器与拦截器链的配置与使用详解,你知道多少?

    往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件详解 SpringBoot系列(四)web静 ...

随机推荐

  1. docker——容器安装tomcat

    写在前面: 继续docker的学习,学习了docker的基本常用命令之后,我在docker上安装jdk,tomcat两个基本的java web工具,这里对操作流程记录一下. 软件准备: 1.jdk-7 ...

  2. 在 ML2 中配置 OVS flat network - 每天5分钟玩转 OpenStack(133)

    前面讨论了 OVS local network,今天开始学习 flat network. flat network 是不带 tag 的网络,宿主机的物理网卡通过网桥与 flat network 连接, ...

  3. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  4. javascript中的事件冒泡和事件捕获

    1.事件冒泡 IE 的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档).以下面的HTML ...

  5. Spring中Bean的作用域、生命周期

                                   Bean的作用域.生命周期 Bean的作用域 Spring 3中为Bean定义了5中作用域,分别为singleton(单例).protot ...

  6. 如何为你的微信小程序体积瘦身?

    众所周知,微信小程序在发布的时候,对提交的代码有1M大小的限制!所以,如果你正在写一个功能稍微复杂一点的小程序,就必须得时刻小心注意你的代码是不是快触及这个底线了. 在设计一个小程序之初,我们就需要重 ...

  7. CRM 数据密钥 忘记 解决方案

    UPDATE EmailServerProfile SET IncomingPassword=nullUPDATE EmailServerProfile SET OutgoingPassword=nu ...

  8. 用Kotlin创建第一个Android项目(KAD 01)

    原文标题:Create your first Android project using Kotlin (KAD 01) 作者:Antonio Leiva 时间:Nov 21, 2016 原文链接:h ...

  9. iOS之App Store上架被拒Legal - 5.1.5问题

    今天在看到App Store 上架过程中,苹果公司反馈的拒绝原因发现了这么一个问题: Legal - 5.1.5 Your app uses background location services ...

  10. mysql 赋予用户权限

    # 赋予权限MySQL> grant 权限参数 on 数据库名称.表名称 to 用户名@用户地址 identified by '用户密码'; # 立即生效权限MySQL> flush pr ...