1. 程式人生 > >談談spring中的攔截器interceptor

談談spring中的攔截器interceptor

談談spring中的攔截器

       在web開發中,攔截器是經常用到的功能。它可以幫我們驗證是否登陸、預先設定資料以及統計方法的執行效率等等。今天就來詳細的談一下spring中的攔截器。spring中攔截器主要分兩種,一個是HandlerInterceptor,一個是MethodInterceptor。

一,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);
			}
		}
	}

       程式碼有點長,但是它封裝了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);
					}
				}
			}
		}
	}
       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總是從當前的攔截器逆向的向前執行。
        另外,實現HandlerInterceptor攔截器還有一個方法,就是實現WebRequestInterceptor介面。其實它和剛才的兩種方法也是殊途同歸,最終還是被spring適配成HandlerInterceptor。有一點不同,它的preHandle方法最終只會返回true。

二,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的實現。

三,談一談區別

        上面的兩種攔截器都能起到攔截的效果,但是他們攔截的目標不一樣,實現的機制不同,所以有的時候適用不同的場景。HandlerInterceptoer攔截的是請求地址,所以針對請求地址做一些驗證、預處理等操作比較合適。當你需要統計請求的響應時間時MethodInterceptor將不太容易做到,因為它可能跨越很多方法或者只涉及到已經定義好的方法中一部分程式碼。MethodInterceptor利用的是AOP的實現機制,在本文中只說明瞭使用方式,關於原理和機制方面介紹的比較少,因為要說清楚這些需要講出AOP的相當一部分內容。在對一些普通的方法上的攔截HandlerInterceptoer就無能為力了,這時候只能利用AOP的MethodInterceptor。         另外,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規範規定的,不屬於spring框架,也是用於請求的攔截。但是它適合更粗粒度的攔截,在請求前後做一些編解碼處理、日誌記錄等。而攔截器則可以提供更細粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。         另外的另外,用過人人網的ROSE框架的人都會非常喜歡它的攔截器功能。因為它實現了全註解的方式,只要在類的名字上加上攔截器的註解即表示這是一個攔截器。而使用這個攔截器的方法或者controller也只需在方法或controller的上面加上這個攔截器的註解。其實這是一個關注點的轉變,spring的切面控制在配置檔案中,配置檔案關注哪些地方需要攔截。而在ROSE中,則是在需要攔截的地方關注我要被誰攔截。 以上純屬個人觀點,歡迎交流!