Spring MVC Interceptor HandlerInterceptorAdapter、HandlerInterceptor 示例

Spring Interceptor 用於攔截客戶端請求並處理它們。有時我們希望攔截 HTTP 請求並在將其交給控制器處理程序方法之前進行一些處理。這就是 Spring MVC Interceptor 發揮作用的地方。

Spring Interceptor

就像我們有 Struts2 Interceptors 一樣,我們可以通過實現 org.springframework.web.servlet.HandlerInterceptor 接口或通過覆蓋提供 HandlerInterceptor 接口的基礎實現的抽象類 org.springframework.web.servlet.handler.HandlerInterceptorAdapter 來創建我們自己的 Spring interceptor。

Spring Interceptor – 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 元素將所有攔截器連線起來。我們還可以提供 URI 模式以在 mapping 元素中匹配,在包含請求的 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 容器並調用 home controller,您將看到類似以下的日誌輸出。

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

以下是翻譯的結果:「輸出確認春季攔截器方法按照定義的順序執行。這就是使用春季攔截器的全部內容,您可以從下面的鏈接下載春季攔截器示例項目,嘗試使用多個攔截器,並通過不同的配置順序進行檢查。」

下載春季攔截器項目

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