SpringMVC教程之自定义拦截器

一、前言

SpringMVC 中的Interceptor 拦截请求是通过HandlerInterceptor 来实现的。在SpringMVC 中定义一个Interceptor 非常简单,主要有两种方式,

  • 第一种方式是要定义的Interceptor类要实现了Spring 的HandlerInterceptor 接口,或者是这个类继承实现了HandlerInterceptor 接口的类,比如Spring 已经提供的实现了HandlerInterceptor 接口的抽象类HandlerInterceptorAdapter
  • 第二种方式是实现Spring的WebRequestInterceptor接口,或者是继承实现了WebRequestInterceptor的类

HandlerInterceptor 接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

  • preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顾名思义,该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
  • postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解释我们知道这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,顾名思义就是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行,这和Struts2 里面的Interceptor 的执行过程有点类似。
  • afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

二、案例

♦自定义一个拦截器类

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
 * @since 2017-11-25
 * @author queen
 *
 */
public class FirstInterceptorHandler implements HandlerInterceptor{

	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		System.out.println("FirstInterceptorHandler preHandle...");
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("FirstInterceptorHandler postHandle...");
	}

	@Override
	public void afterCompletion(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("FirstInterceptorHandler afterCompletion...");
	}

}



♦自定义一个处理器

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @since 2017-11-25
 * @author queen
 *
 */
@Controller
@RequestMapping(value = "myhandler")
public class MyHandler {
	// 处理器方法
    @RequestMapping(value = "hello")
    public String welocome() {
        // 所有文件上传
        System.out.println("Hello World!");
        return "hello";
    }
}

♦配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
	<!-- 定义Controller的扫描包 -->
	<context:component-scan base-package="com.queen.springmvc" />

	<!-- 配置视图解析器 -->
	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/" />
		<property name="suffix" value=".jsp" />
	</bean>

	<mvc:annotation-driven />

	<mvc:interceptors>
		<bean class="com.queen.springmvc.handlers.FirstInterceptorHandler"></bean>
	</mvc:interceptors>
</beans>

♦web.xml中配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<welcome-file-list>
		<welcome-file>welcome.jsp</welcome-file>
	</welcome-file-list>

	<!-- 配置DispatcherServlet -->
	<servlet>
		<servlet-name>dispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<!-- 配置DispatcherServlet的一个初始化参数:配置SpringMVC配置文件的位置和名称 -->
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>dispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

♦跳转页面

<%@ page language="java" contentType="text/html; charset=UTF-8"  
    pageEncoding="UTF-8"%>  
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">  
<html>  
<head>  
<title>欢迎界面</title>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
</head>  
<body>  
    <center>  
       <a href="myhandler/hello">Test Hello</a>  
    </center>
</body>  
</html> 

♦测试运行

SpringMVC教程之自定义拦截器的照片 - 1

再看一下控制台打印效果:

FirstInterceptorHandler preHandle...
Hello World!
FirstInterceptorHandler postHandle...
FirstInterceptorHandler afterCompletion...

♦下面我们通过源码看看为什么打印是如上的结果,这三个方法是什么时候被调用的?

在FirstInterceptorHandler的16,23,30行处打上断点

SpringMVC教程之自定义拦截器的照片 - 3

在MyHandler的16行处打上断点

SpringMVC教程之自定义拦截器的照片 - 5

在DispatcherServlet的doDispatch方法处打上断点,这里是所有请求的入口

SpringMVC教程之自定义拦截器的照片 - 7

启动服务,再次运行,断点进入900行这里

从900行到937行,与本文所讲环节没有关系,直接略过,断点到938行

SpringMVC教程之自定义拦截器的照片 - 9

 

从字面上来看调用的是preHandler方法。如果applyPreHandler这段方法返回的是false,那么直接return了,return之后,后面的目标方法不能执行了。也可以得出一个结论:preHandler方法在目标方法之前被调用。在preHandler方法这里我们可以做些类似权限,日志等的处理

我们来看一下applyPreHandler方法

/**
 * 可以看出来,这段方法是对我们配置的所有拦截器进行遍历处理,在遍历的过程中尝试着调用每一个
 * 拦截器的preHandler方法,如果调用返回false就直接 return false,后续的拦截器不会被调用,而是
 * 直接调用的triggerAfterCompletion方法释放资源
 * 
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (getInterceptors() != null) {
		for (int i = 0; i < getInterceptors().length; i++) {
			HandlerInterceptor interceptor = getInterceptors()[i];
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
	}
	return true;
}

从上面的代码我们可以得出一个结论:如果某一个拦截器的preHandler方法返回了false,那么后续拦截器也不会被调用

我们尝试着把FirstInterceptorHandler的preHandle方法返回值改为true,这时候运行又是什么效果呢?

SpringMVC教程之自定义拦截器的照片 - 11

可以先看一下,控制台打印效果:

FirstInterceptorHandler preHandle...

控制台就打印了一句话,跟上面正常的相比,少了三句。

好了,现在我们回到正常的上面来,当我们执行完applyPreHandler方法后,就会执行下面这段方法

// 我们都知道这段方法,就是调用目标方法的,调用完之后界面会打印:Hello World!
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

继续往下面执行,断点到950行处

SpringMVC教程之自定义拦截器的照片 - 13

从执行的结果和断点流程来看我们又得出一个结论:postHandler方法是在目标方法调用之后才被调用但是渲染视图之前被调用。processDispatchResult这段方法是渲染视图用的。在postHandler方法这里我们可以修改请求域里面的属性或者修改要转向的视图。

现在preHandler和postHandler方法我们都知道了调用时机,那么afterCompletion方法是何时被调用的呢?

其实是在processDispatchResult方法里面被调用的,继续往下面走,断点到processDispatchResult方法的1029行

SpringMVC教程之自定义拦截器的照片 - 15

从如上代码可知render(mv, request, response);方法在mappedHandler.triggerAfterCompletion(request, response, null);之前被调用,所以我们又可以得出一个结论:afterCompletion方法在视图渲染之后被调用的。在afterCompletion方法里面主要是释放资源作用。

从如上的过程,我们画一个流程图如下:

SpringMVC教程之自定义拦截器的照片 - 17

至此,我们关于SpringMVC教程之自定义拦截器介绍完毕。想要了解更多关于SpringMVC开发的教程,请参考http://www.marsitman.com/springmvc
博客地址:http://www.marsitman.com/springmvc/springmvc-interceptor.html
版权声明:本文为博主原创文章,允许转载,但转载必须标明出处。

 

 

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!



点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注