Spring MVC拦截器HandlerInterceptorAdapter,HandlerInterceptor示例

拦截器用于拦截客户端请求并处理它们。有时,我们希望在将HTTP请求交给控制器处理程序方法之前拦截并进行一些处理。这就是Spring MVC拦截器派上用场的地方。

Spring拦截器

就像我们有Struts2拦截器一样,我们可以通过实现org.springframework.web.servlet.HandlerInterceptor接口或通过覆盖抽象类org.springframework.web.servlet.handler.HandlerInterceptorAdapter来创建自己的Spring拦截器,该抽象类提供了HandlerInterceptor接口的基本实现。

Spring拦截器 – HandlerInterceptor

Spring HandlerInterceptor根据我们想要拦截HTTP请求的位置声明了三个方法。

  1. boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):此方法用于在请求交给处理程序方法之前拦截请求。此方法应返回’true’,以便让Spring知道通过另一个Spring拦截器处理请求,或者如果没有更多的Spring拦截器,则将其发送到处理程序方法。如果此方法返回’false’,Spring框架会假定请求已由Spring拦截器本身处理,不需要进一步处理。在这种情况下,我们应该使用响应对象将响应发送给客户端请求。对象handler是处理请求的所选处理程序对象。此方法也可能会抛出异常,在这种情况下,Spring MVC异常处理应该有助于将错误页面作为响应发送。
  2. void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):当HandlerAdapter调用处理程序但DispatcherServlet尚未呈现视图时,将调用此HandlerInterceptor拦截器方法。此方法可用于向ModelAndView对象添加附加属性,以在视图页面中使用。我们可以使用此Spring拦截器方法来确定处理程序方法处理客户端请求所花费的时间。
  3. void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):这是一个HandlerInterceptor回调方法,当处理程序执行并呈现视图时调用。

如果配置了多个spring拦截器,则按照配置顺序执行preHandle()方法,而postHandle()afterCompletion()方法则以相反的顺序调用。让我们创建一个简单的Spring MVC应用程序,在其中配置一个Spring拦截器来记录控制器处理程序方法的时间。我们最终的Spring拦截器示例项目将如下图所示,我们将研究我们感兴趣的组件。

Spring拦截器 – 控制器类

package com.journaldev.spring;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/home", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		logger.info("Welcome home! The client locale is {}.", locale);
		//添加一些时间延迟以检查拦截器执行
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Date date = new Date();
		DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
		
		String formattedDate = dateFormat.format(date);
		
		model.addAttribute("serverTime", formattedDate );
		logger.info("Before returning view page");
		return "home";
	}
	
}

I am just adding some processing time in the execution of the handler method to check our spring interceptor methods in action.

Spring MVC拦截器 – HandlerInterceptorAdapter实现

为了简便起见,我正在扩展抽象类HandlerInterceptorAdapter。HandlerInterceptorAdapter是HandlerInterceptor接口的抽象适配器类,用于简化仅前置/仅后置拦截器的实现。

package com.journaldev.spring;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class RequestProcessingTimeInterceptor extends HandlerInterceptorAdapter {

	private static final Logger logger = LoggerFactory
			.getLogger(RequestProcessingTimeInterceptor.class);

	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		long startTime = System.currentTimeMillis();
		logger.info("Request URL::" + request.getRequestURL().toString()
				+ ":: Start Time=" + System.currentTimeMillis());
		request.setAttribute("startTime", startTime);
		//如果返回false,我们需要确保 'response' 被发送
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("Request URL::" + request.getRequestURL().toString()
				+ " Sent to Handler :: Current Time=" + System.currentTimeMillis());
		//我们可以在modelAndView中添加属性,并在视图页面中使用
	}

	@Override
	public void afterCompletion(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		long startTime = (Long) request.getAttribute("startTime");
		logger.info("Request URL::" + request.getRequestURL().toString()
				+ ":: End Time=" + System.currentTimeMillis());
		logger.info("Request URL::" + request.getRequestURL().toString()
				+ ":: Time Taken=" + (System.currentTimeMillis() - startTime));
	}

}

逻辑非常简单,我只是记录处理程序方法执行的时间和处理请求所需的总时间,包括渲染视图页面。

Spring MVC拦截器配置

我们必须将Spring拦截器绑定到请求上,可以使用mvc:interceptors元素来绑定所有拦截器。我们还可以通过mapping元素为请求提供URI模式,以在包含Spring拦截器之前匹配。我们的最终Spring Bean配置文件(spring.xml)如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
	xmlns:context="https://www.springframework.org/schema/context"
	xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing 
		infrastructure -->

	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving 
		up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources 
		in the /WEB-INF/views directory -->
	<beans:bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>

	<!-- Configuring interceptors based on URI -->
	<interceptors>
		<interceptor>
			<mapping path="/home" />
			<beans:bean class="com.journaldev.spring.RequestProcessingTimeInterceptor"></beans:bean>
		</interceptor>
	</interceptors>

	<context:component-scan base-package="com.journaldev.spring" />

</beans:beans>

I will not explain all other components of the web application, because we are not interested in them and they don’t have any specific spring interceptor related configuration.

Spring MVC拦截器应用测试

只需部署应用程序到Servlet容器并调用主控制器,您将看到类似以下的记录器输出。

INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: Start Time=1396906442086
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is en_US.
INFO : com.journaldev.spring.HomeController - Before returning view page
Request URL::https://localhost:9090/SpringInterceptors/home Sent to Handler :: Current Time=1396906443098
INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: End Time=1396906443171
INFO : com.journaldev.spring.RequestProcessingTimeInterceptor - Request URL::https://localhost:9090/SpringInterceptors/home:: Time Taken=1085

输出确认了按照定义的顺序执行了Spring拦截器方法。关于使用Spring拦截器的内容就这些了,你可以从下面的链接下载Spring拦截器示例项目,尝试使用多个拦截器并通过不同的配置顺序进行检查。

下载Spring拦截器项目

Source:
https://www.digitalocean.com/community/tutorials/spring-mvc-interceptor-example-handlerinterceptor-handlerinterceptoradapter