(转)spring中的拦截器(HandlerInterceptor+MethodInterceptor)

1.  过滤器跟拦截器的区别

在说拦截器之前,不得不说一下过滤器,有时候往往被这两个词搞的头大。

其实我们最先接触的就是过滤器,还记得web.xml中配置的<filter>吗~

你应该知道spring mvc的拦截器是只拦截controller而不拦截jsp,html 页面文件的,如果想要拦截那怎么办?

这就用到过滤器filter了,filter是在servlet前执行的,你也可以理解成过滤器中包含拦截器,一个请求过来 ,先进行过滤器处理,看程序是否受理该请求 。 过滤器放过后 , 程序中的拦截器进行处理 。

(1)过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)

(2)拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)。

2.  spring中的拦截器

在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等等。

今天就来详细的谈一下spring中的拦截器。spring中拦截器主要分两种,一个是HandlerInterceptor,一个是MethodInterceptor。

2.1  HandlerInterceptor拦截器

HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。

实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。

这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。

下面就是HandlerInterceptorAdapter的代码,可以看到一个方法只是默认返回true,另外两个是空方法:

public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {  /** * This implementation always returns <code>true</code>. */  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  throws Exception {  return true;  }  /** * This implementation is empty. */  public void postHandle(  HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)  throws Exception {  }  /** * This implementation is empty. */  public void afterCompletion(  HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  throws Exception {  }  }  

这三个方法都是干什么的,有什么作用,什么时候调用,不同的拦截器之间是怎样的调用顺序呢?

先补一张图:

这还得参考一下DispatcherServlet的doDispatch方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  HttpServletRequest processedRequest = request;  HandlerExecutionChain mappedHandler = null;  int interceptorIndex = -1;  try {  ModelAndView mv;  boolean errorView = false;  try {  processedRequest = checkMultipart(request);  // Determine handler for the current request.  mappedHandler = getHandler(processedRequest, false);  if (mappedHandler == null || mappedHandler.getHandler() == null) {  noHandlerFound(processedRequest, response);  return;  }  // Determine handler adapter for the current request.  HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  // Process last-modified header, if supported by the handler.  String method = request.getMethod();  boolean isGet = "GET".equals(method);  if (isGet || "HEAD".equals(method)) {  long lastModified = ha.getLastModified(request, mappedHandler.getHandler());  if (logger.isDebugEnabled()) {  String requestUri = urlPathHelper.getRequestUri(request);  logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);  }  if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {  return;  }  }  // Apply preHandle methods of registered interceptors.  HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();  if (interceptors != null) {  for (int i = 0; i < interceptors.length; i++) {  HandlerInterceptor interceptor = interceptors[i];  if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {  triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  return;  }  interceptorIndex = i;  }  }  // Actually invoke the handler.  mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  // Do we need view name translation?  if (mv != null && !mv.hasView()) {  mv.setViewName(getDefaultViewName(request));  }  // Apply postHandle methods of registered interceptors.  if (interceptors != null) {  for (int i = interceptors.length - 1; i >= 0; i--) {  HandlerInterceptor interceptor = interceptors[i];  interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);  }  }  }  catch (ModelAndViewDefiningException ex) {  logger.debug("ModelAndViewDefiningException encountered", ex);  mv = ex.getModelAndView();  }  catch (Exception ex) {  Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  mv = processHandlerException(processedRequest, response, handler, ex);  errorView = (mv != null);  }  // Did the handler return a view to render?  if (mv != null && !mv.wasCleared()) {  render(mv, processedRequest, response);  if (errorView) {  WebUtils.clearErrorRequestAttributes(request);  }  }  else {  if (logger.isDebugEnabled()) {  logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +  "': assuming HandlerAdapter completed request handling");  }  }  // Trigger after-completion for successful outcome.  triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);  }  catch (Exception ex) {  // Trigger after-completion for thrown exception.  
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  throw ex;  }  catch (Error err) {  ServletException ex = new NestedServletException("Handler processing failed", err);  // Trigger after-completion for thrown exception.  
            triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  throw ex;  }  finally {  // Clean up any resources used by a multipart request.  if (processedRequest != request) {  cleanupMultipart(processedRequest);  }  }  }  
View Code

代码有点长,但是它封装了springMVC处理请求的整个过程。首先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器;然后在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。关于拦截器的处理到此为止,接下来看看triggerAfterCompletion做了什么:

private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,  int interceptorIndex,  HttpServletRequest request,  HttpServletResponse response,  Exception ex) throws Exception {  // Apply afterCompletion methods of registered interceptors.  if (mappedHandler != null) {  HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();  if (interceptors != null) {  for (int i = interceptorIndex; i >= 0; i--) {  HandlerInterceptor interceptor = interceptors[i];  try {  interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);  }  catch (Throwable ex2) {  logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);  }  }  }  }  }  
View Code

 triggerAfterCompletion做的事情就是从当前的拦截器开始逆向调用每个拦截器的afterCompletion方法,并且捕获它的异常,也就是说每个拦截器的afterCompletion方法都会调用。

根据以上的代码,分析一下不同拦截器及其方法的执行顺序。假设有5个拦截器编号分别为12345,若一切正常则方法的执行顺序是12345的preHandle,54321的postHandle,54321的afterCompletion。若编号3的拦截器的preHandle方法返回false或者抛出了异常,接下来会执行的是21的afterCompletion方法。这里要注意的地方是,我们在写一个拦截器的时候要谨慎的处理preHandle中的异常,因为这里一旦有异常抛出就不会再受到这个拦截器的控制。12345的preHandle的方法执行过之后,若handler出现了异常或者某个拦截器的postHandle方法出现了异常,则接下来都会执行54321的afterCompletion方法,因为只要12345的preHandle方法执行完,当前拦截器的拦截器就会记录成编号5的拦截器,而afterCompletion总是从当前的拦截器逆向的向前执行。

2.2  MethodInterceptor拦截器

MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。
下面是第一种方法的示例
public class MethodInvokeInterceptor implements MethodInterceptor {  @Override  public Object invoke(MethodInvocation methodInvocation) throws Throwable {  System.out.println("before method invoke");  Object object = methodInvocation.proceed();  System.out.println("after method invoke");  return object;  }  
}  

下面是基于注解的AspectJ方式

@Component  
@Aspect  
public class AutoAspectJInterceptor {  @Around("execution (* com.test.controller..*.*(..))")  public Object around(ProceedingJoinPoint point) throws Throwable{  System.out.println("AutoAspectJInterceptor begin around");  Object object = point.proceed();  System.out.println("AutoAspectJInterceptor end around");  return object;  }  }  

下面是一个用于支持AspectJ方式拦截的普通的bean,当然你也可以在配置文件中声明这个bean

@Component  public class AspectJInterceptor {  public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  System.out.println("AspectJInterceptor around before");  Object object = proceedingJoinPoint.proceed();  System.out.println("AspectJInterceptor around after");  return object;  }  
}  

当然,这一切都离不开配置,具体看配置中的注释

<!-- 自定义拦截器 ,先过mvc:interceptors-->  <bean id="methodInvokeInterceptor" class="com.test.interceptor.MethodInvokeInterceptor"/>  <bean id="aspectInterceptor" class="com.test.interceptor.AspectJInterceptor"/>  <aop:config>  <!--切入点,controlller -->  <aop:pointcut id="pointcut_test"   expression="execution(* com.test.controller..*.*(..))" />  <!--在该切入点使用自定义拦截器 ,按照先后顺序执行 -->  <aop:advisor pointcut-ref="pointcut_test" advice-ref="methodInvokeInterceptor" />  <aop:aspect ref="aspectInterceptor">  <aop:around method="around" pointcut="execution(* com.test.controller..*.*(..))"/>  </aop:aspect>  </aop:config>  <!-- 自动扫描使用了aspectj注解的类 -->  <aop:aspectj-autoproxy/>  

通过上面的配置三个MethodInterceptor就能正常工作了。其实,这两种实现方最终...没错,还是殊途同归。

aspectj的拦截器会被解析成AOP中的advice,最终被适配成MethodInterceptor,详细的过程请参考springAOP的实现。

3.  实例选择拦截器

项目中采用Interceptor来过滤URL来决定哪些可以在不登录的情况下访问,哪些必须要登录才可以访问;

3.1  HandlerInterceptor方式

public class SessionTimeoutInterceptor implements HandlerInterceptor {.........
}

此时需要在servlet.xml中配置<mvc:interceptor>

 

3.2  MethodInterceptor注解Aspect方式

@Component
@Aspect
public void class BindingResultAop{........
}

同时在servlet.xml中配置
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

经测试发现,interceptor先于AOP执行。

4.  谈一谈区别

上面的两种拦截器都能起到拦截的效果,但是他们拦截的目标不一样,实现的机制不同,所以有的时候适用不同的场景。

HandlerInterceptoer拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。当你需要统计请求的响应时间时MethodInterceptor将不太容易做到,因为它可能跨越很多方法或者只涉及到已经定义好的方法中一部分代码。MethodInterceptor利用的是AOP的实现机制,在本文中只说明了使用方式,关于原理和机制方面介绍的比较少,因为要说清楚这些需要讲出AOP的相当一部分内容。在对一些普通的方法上的拦截HandlerInterceptoer就无能为力了,这时候只能利用AOP的MethodInterceptor。


另外,还有一个跟拦截器类似的东西----Filter。Filter是Servlet规范规定的,不属于spring框架,也是用于请求的拦截。但是它适合更粗粒度的拦截,在请求前后做一些编解码处理、日志记录等。而拦截器则可以提供更细粒度的,更加灵活的,针对某些请求、某些方法的组合的解决方案。


另外的另外,用过人人网的ROSE框架的人都会非常喜欢它的拦截器功能。因为它实现了全注解的方式,只要在类的名字上加上拦截器的注解即表示这是一个拦截器。而使用这个拦截器的方法或者controller也只需在方法或controller的上面加上这个拦截器的注解。其实这是一个关注点的转变,spring的切面控制在配置文件中,配置文件关注哪些地方需要拦截。而在ROSE中,则是在需要拦截的地方关注我要被谁拦截。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/414772.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core源码学习(一)Hosting

ASP.NET Core源码的学习&#xff0c;我们从Hosting开始&#xff0c; Hosting的GitHub地址为&#xff1a;https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆或是下载。 为什么是从Hosting开始学习呢&#xff1f;我们来看看ASP.NET Core MVC项目中的Pragram.CS中的…

前端学习(2715):重读vue电商网站35之在sessionStorage保存左侧菜单栏的激活状态

为了让我们点击二级菜单时&#xff0c;会有一个激活效果&#xff0c;而且是保持状态&#xff0c;我们需要将利用 element-ui 菜单栏相关 default-active 属性&#xff0c;来让菜单保持激活。 由于每个二级菜单都有一个路由链接&#xff0c;不妨将路由路径 path 存储在 sessionS…

Struts2的通配符配置方式

Struts2的Action类很有意思&#xff0c;你可以使用3种方式来实现具体的Action类&#xff1a; 让你的Action类继承自ActionSupport类&#xff08;项目中最常用这种方式&#xff0c;因为ActionSupport类中定义了很多帮助方法&#xff09;让你的Action类实现Action接口使用POJO的…

spring拦截器-过滤器的区别

1. 理解 拦截器 &#xff1a;是在面向切面编程的时候&#xff0c;在你的 service 或者一个方法前调用一个方法&#xff0c;或者在方法后调用一个方法&#xff1b;比如动态代理就是拦截器的简单实现&#xff0c;在你调用方法前打印出字符串&#xff08;或者做其它业务逻辑的操作…

前端学习(2716):重读vue电商网站36之slot插槽使用

项目需求&#xff1a;由于用户列表状态后台返回的是 true/false&#xff0c;无法进行渲染&#xff0c;而我们需要的是有一个Switch开关来控制我们的状态。添加一个template 模板后&#xff0c;此时就可以用 slot-scope 作用域插槽来获取我们数据列表中的每一行数据&#xff0c;…

jsp调用struts,jsp调用action,action获取表单提交的参数

自定义action类。LgqAction&#xff1a; Component Scope(value"prototype") public class LgqAction { private Connection conn null; private PreparedStatement pstmt null; private ResultSet rs null; public String execut…

员工信息增删改查

1 #Author guixin2 def find():3 4 查询语法如下&#xff1a;5 find name,age from staff_table where age > 226 find * from staff_table where dept IT7 find * from staff_table where enroll_date like 20138 9 data input("查询:&…

springmvc.xml 中 url-pattern/url-pattern节点详解

1. 先来上段常见的代码 1 <!-- MVC Servlet -->2 <servlet>3 <servlet-name>springServlet</servlet-name>4 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>5 <…

前端学习(2717):重读vue电商网站37之通过switch开关更改用户状态

首先&#xff0c;在 switch 开关添加一个 change 事件&#xff0c;并且通过作用域插槽的形式&#xff0c;将该行数据作为参数传入&#xff0c;目的是为了后续的修改。 在函数内我们将传递过来的参数作为我们请求的参数&#xff0c;通过 put 方式修改我们的后台数据 。

边框回归(Bounding Box Regression)详解

原文地址&#xff1a;http://blog.csdn.net/zijin0802034/article/details/77685438 Bounding-Box regression 最近一直看检测有关的Paper, 从rcnn&#xff0c; fast rcnn, faster rcnn, yolo, r-fcn, ssd&#xff0c;到今年cvpr最新的yolo9000。这些paper中损失函数都包含了边…

前端学习(2718):重读vue电商网站38之通过input输入框优化

通过增加 clearable 属性&#xff0c;我们的输入框就可以多一个 x&#xff0c;然后通过绑定 clear 事件&#xff0c;当我们进行清除 &#xff08;即点击由 clearable 属性生成的清空按钮时触发)时&#xff0c;就会重新获取我们的用户列表&#xff0c;不再需要用户再次点击搜索按…

web项目Servlet配置及jsp访问Servlet

方法1&#xff1a;单一方法请求servlet 1、创建Servlet WebServlet("/HelloForm") public class HelloForm extends HttpServlet { private static final long serialVersionUID 1L; /** * see HttpServlet#HttpServlet() */ publi…

php与数据库的连接用法 (签到一)

注册页面 //插入js验证 <script type"text/javascript"> window.onload function(){ var hid document.getElementById("id"); if(hid.value !""){ //当用户名已存在数据…

Oracle不连续的值,如何实现查找上一条、下一条

1. 遇到的问题 已知一个题库&#xff0c;希望实现当前页切换上一题&#xff0c;下一题的需求。 查看得知&#xff0c;数据库中用于查询的字段(主键)是不连续的。如上图所示&#xff1a;stxh为主键number类型。 2. 实现方式lead over 2.1 实现代码 下一条 select nowId, afte…

前端学习(2719):重读vue电商网站39之正则表达式验证邮箱和手机号码

Javascript // 验证邮箱的规则var checkEmail (rule, value, cb) > {const regEmail /^([a-zA-Z]|[0-9])(\w|-)[a-zA-Z0-9]\.([a-zA-Z]{2,4})$/if (regEmail.test(value)) {// 合法的邮箱return cb()}cb(new Error(请输入合法的邮箱))}// 验证手机号码的规则var checkMobi…

支付宝提现,单笔转账到支付宝账户

很简单。只需三个参数实现 单笔转账到支付宝账户 1、获取开放平台创建的APPID&#xff0c;同时必须添加 单笔转账到支付宝账户 这个功能 开放平台&#xff1a;https://open.alipay.com/platform/home.htm 2、登录支付宝商家中心平台&#xff1a;https://b.alipay.com/index.h…

Flink系列之:Checkpoints

Flink系列之&#xff1a;Checkpoints 一、概述二、保留Checkpoint三、目录结构四、通过配置文件全局配置五、创建 state backend 对单个作业进行配置六、从保留的checkpoint 中恢复状态 一、概述 Checkpoint 使 Flink 的状态具有良好的容错性&#xff0c;通过 checkpoint 机制…

filter过滤器实现验证跳转_返回验证结果

1. 需求背景 需要对某个请求url进行拦截&#xff0c;模拟是否可以进入某一个接口&#xff0c;如果拦截需要返回数据false&#xff0c;别问我为何不用intercept拦截器。 2. web.xml <filter> <filter-name>restfulFilter</filter-name> <filter-clas…

学习基础和C语言基础调查

一.你有什么技能比大多人&#xff08;超过90%以上&#xff09;更好&#xff1f; 答&#xff1a;回答这个问题之前应该加上“我觉得”三个字&#xff1b;其实我没什么太多太实用的技能&#xff0c;无非就是一些特别的、没什么新鲜有意义的技能&#xff0c;比如说我觉得有一些不太…

前端学习(2730):重读vue电商网站40之使用vue-table-with-tree-grid

安装新的依赖 vue-tabel-with-tree-gridvue-tabel-with-tree-grid 官方文档 安装完成后&#xff0c;在 main.js 入口文件内先导入 tree-tabel 然后全局注册组件 tree-tabel 页面中&#xff0c;我们使用了如下属性&#xff1a; data 确定我们的数据源&#xff0c;columns定义我…