6. ModelDriven拦截器、Preparable 拦截器
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>
- 根据上面的结果,我们需要做的操作是:
- 先调用 param 拦截器封装一部分属性进入Action
- 再调用
modelDriven拦截器进入值栈 - 在 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操作是,delete和update方法都带有 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 拦截器的更多相关文章
- struts2 ModelDriven 和 Preparable 拦截器
Struts2 运行流程图-1
- ModelDriven 和 Preparable 拦截器
Params 拦截器 Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueSta ...
- Struts2 - ModelDriven 拦截器、Preparable 拦截器
开篇:拦截器在Struts中的作用 在我们的web.xml中,我们配置了一个过滤器,实现将所有请求交付StrutsPrepareAndExecuteFilter类.一旦接受到任意action的请求,该 ...
- Struts2-整理笔记(五)拦截器、拦截器配置
拦截器(Interceptor) 拦截器是Struts2最强大的特性之一,它是一种可以让用户在Action执行之前和Result执行之后进行一些功能处理的机制. 拦截器的优点 简化了Action的实现 ...
- SpringMCVC拦截器不拦截静态资源
SpringMCVC拦截器不拦截静态资源 SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下 ...
- spring拦截器不拦截方法名原因
开发一个基于注解的登录拦截器,遇到拦截器只能拦截controller不能拦截到具体的方法名,这样拦截器就完全没用,经过仔细摸索,DefaultAnnotationHandlerMapping和Anno ...
- Spring boot自定义拦截器和拦截器重定向配置简单介绍
大家好: 本文简单介绍一下用于权限控制的Spring boot拦截器配置,拦截器重定向问题. 开发工具:jdk1.8 idea2017(付费版,网上找的破解教程) 1,首先使用idea创建一个Sp ...
- springboot拦截器的拦截配置和添加多个拦截器
在spring2.0之前的版本大部分都采用extends WebMvcConfigurerAdapter,把拦截器配置成一个bean,具体的方法,我不细说,网上一大堆.而在spring2.0之后,这个 ...
- SpringBoot系列(十一)拦截器与拦截器链的配置与使用详解,你知道多少?
往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件详解 SpringBoot系列(四)web静 ...
随机推荐
- nohup程序后台执行
Linux常用命令,用于不挂断的执行程序. nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令.该命令可以在你退出帐户/关闭终端之后继续运行相应 ...
- centos7+mono4+jexus5.6.2安装过程中的遇到的问题
过程参考: http://www.linuxdot.net/ http://www.jexus.org/ http://www.mono-project.com/docs/getting-starte ...
- Node.js:理解stream
Stream在node.js中是一个抽象的接口,基于EventEmitter,也是一种Buffer的高级封装,用来处理流数据.流模块便是提供各种API让我们可以很简单的使用Stream. 流分为四种类 ...
- CSS 3 学习——渐变
通过CSS渐变创建的是一个没有固定比例和固定尺寸的<image>类型,也就是说是一张图片,这张图片的尺寸由所应用的元素的相关信息决定.凡是支持图片类型的CSS属性都可以设置渐变,而支持颜色 ...
- TYPESDK手游聚合SDK服务端设计思路与架构之一:应用场景分析
TYPESDK 服务端设计思路与架构之一:应用场景分析 作为一个渠道SDK统一接入框架,TYPESDK从一开始,所面对的需求场景就是多款游戏,通过一个统一的SDK服务端,能够同时接入几十个甚至几百个各 ...
- ios 获取或修改网页上的内容
UIWebView是iOS最常用的SDK之一,它有一个stringByEvaluatingJavaScriptFromString方法可以将javascript嵌 入页面中,通过这个方法我们可 ...
- Lucene4.4.0 开发之排序
排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最 ...
- SQL Server 批量删除存储过程
原理很简单的'drop proc xxx'即可,下面有提供了两种方式来删除存储过程,其实本质是相同的,方法一是生成删除的sql后直接执行了,方法二会生成SQL,但需要检查后执行,个人推荐第二种做法. ...
- windows 7(32/64位)GHO安装指南(U盘引导篇)~
上一篇我们说了怎么制作U盘启动盘,那么这一篇让我们来看看如何进行正确的U盘引导启动. 现在的个人计算机一般分为台式机和笔记本,由于各厂商的喜好不同(开玩笑的啦),所以对于主板的BIOS设置各所不同.进 ...
- TCP服务和首部知识点小结
服务 应用程序会被TCP分割成数据段,而UDP不分割. TCP有超时重传和确认 如果检验和出错将丢弃 IP数据包可能会失序或者重复,所以TCP会处理 滑动窗口来进行流量控制 对字节流的内容不做任何解释 ...