# 自定義攔截器及原理

1. 用法

博客索引web

1.1 繼承 HandlerInterceptor

至於爲何要繼承這個類,下面講解原理的時候會提到。 咱們寫一個簡單的HelloInterceptor攔截器,輸出hellospring

public class HelloInterceptor implements HandlerInterceptor {

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

}
複製代碼

1.2 建立一個配置類WebConfiguration來繼承WebMvcConfigurer,以下:

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Bean
    HelloInterceptor interceptor() {
        return new HelloInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //註冊攔截器HelloInterceptor,攔截全部請求,除了/test
        registry.addInterceptor(interceptor()).addPathPatterns("/**").excludePathPatterns("/test");
    }

}
複製代碼

1.3 大功告成,測試。

@RestController
public class MvcController {

    @GetMapping("/test")
    private String test() {
        System.out.println("i am test ......");
        return "test";
    }

    @GetMapping("/test1")
    private String test1() {
        System.out.println("i am test1 ......");
        return "test1";
    }
}
複製代碼

訪問localhost:8080/test 輸出i am test ...... 訪問localhost:8080/test1 輸出 i am HelloInterceptor i am test ......數組

2. 原理

直接看配置類中的方法。bash

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        //註冊攔截器HelloInterceptor,攔截全部請求,除了/test
        registry.addInterceptor(interceptor()).addPathPatterns("/**").excludePathPatterns("/test");
    }
複製代碼

點進去發現。InterceptorRegistry裏面有個registrations對象是一個InterceptorRegistration類型的攔截器列表,addInterceptor(HandlerInterceptor)方法將攔截器包裝成InterceptorRegistration對象並添加到registrations對象。而後還會發現裏面有個getInterceptors()方法返回全部的攔截器。咱們用idea搜索一下,看那些地方調用這個方法。mvc

public class InterceptorRegistry {

	private final List<InterceptorRegistration> registrations = new ArrayList<>();

	public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
		InterceptorRegistration registration = new InterceptorRegistration(interceptor);
		this.registrations.add(registration);
		return registration;
	}

	public InterceptorRegistration addWebRequestInterceptor(WebRequestInterceptor interceptor) {
		WebRequestHandlerInterceptorAdapter adapted = new WebRequestHandlerInterceptorAdapter(interceptor);
		InterceptorRegistration registration = new InterceptorRegistration(adapted);
		this.registrations.add(registration);
		return registration;
	}

	/**
	 * 返回全部註冊的攔截器
	 */
	protected List<Object> getInterceptors() {
		return this.registrations.stream()
				.sorted(INTERCEPTOR_ORDER_COMPARATOR)
				// 這裏很關鍵,類型是MappedInterceptor
				.map(InterceptorRegistration::getInterceptor)
				.collect(Collectors.toList());
	}

	private static final Comparator<Object> INTERCEPTOR_ORDER_COMPARATOR =
			OrderComparator.INSTANCE.withSourceProvider(object -> {
				if (object instanceof InterceptorRegistration) {
					return (Ordered) ((InterceptorRegistration) object)::getOrder;
				}
				return null;
			});

}
複製代碼

org.springframework.web.servlet.config.annotation.InterceptorRegistration#getInterceptor(): 返回MappedInterceptor類型的攔截器,返回值yongObject接收,也就是說咱們自定義的攔截器會被包裝成MappedInterceptor類型,而MappedInterceptor又繼承了HandlerInterceptor,還提了匹配URL的功能,便於各類自定義開發。app

protected Object getInterceptor() {
		if (this.includePatterns.isEmpty() && this.excludePatterns.isEmpty()) {
			return this.interceptor;
		}
        // 攔截路徑的數組,好比咱們自定義的HelloInterceptor,那麼這裏的include=["/**"]
		String[] include = StringUtils.toStringArray(this.includePatterns);
		// 排除路徑的數組,這裏的exclude=["/test"]
		String[] exclude = StringUtils.toStringArray(this.excludePatterns);
		MappedInterceptor mappedInterceptor = new MappedInterceptor(include, exclude, this.interceptor);
		if (this.pathMatcher != null) {
			mappedInterceptor.setPathMatcher(this.pathMatcher);
		}
		return mappedInterceptor;
	}
複製代碼

發現 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getInterceptors()調用了以前的

protected final Object[] getInterceptors(
			FormattingConversionService mvcConversionService,
			ResourceUrlProvider mvcResourceUrlProvider) {
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
			// 獲取全部註冊過的攔截器
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}
複製代碼

繼續搜索,看哪一個地方調用這個getInterceptors()方法。cors

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping: 建立 RequestMappingHandlerMapping

@Bean
	@SuppressWarnings("deprecation")
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
		// 給RequestMappingHandlerMapping設置攔截器
		mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));

		···省略其餘

		return mapping;
	}
複製代碼

org.springframework.web.servlet.handler.AbstractHandlerMapping#setInterceptors(Object... interceptors):抽象類,封裝了RequestMappingHandlerMapping大部分方法,能夠理解成是一種模板模式,其中幾個重要的方法。async

  1. initInterceptors(): 初始化攔截器,將interceptors對象裏面的攔截器添加到adaptedInterceptors中,這裏能夠看出添加的攔截器必須是HandlerInterceptor類型或者WebRequestInterceptor,要否則就會拋出異常。這就解答了咱們自定義異常爲何要繼承HandlerInterceptor
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {

	@Nullable
	private Object defaultHandler;

	private UrlPathHelper urlPathHelper = new UrlPathHelper();

	private PathMatcher pathMatcher = new AntPathMatcher();

	private final List<Object> interceptors = new ArrayList<>();

	private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
	
    public void setInterceptors(Object... interceptors) {
    		this.interceptors.addAll(Arrays.asList(interceptors));
    	}

    // 適配攔截器
	protected HandlerInterceptor adaptInterceptor(Object interceptor) {
		if (interceptor instanceof HandlerInterceptor) {
			return (HandlerInterceptor) interceptor;
		}
		else if (interceptor instanceof WebRequestInterceptor) {
			return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
		}
		else {
			throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
		}
	}

    // 初始化攔截器
    protected void initInterceptors() {
    		if (!this.interceptors.isEmpty()) {
    			for (int i = 0; i < this.interceptors.size(); i++) {
    				Object interceptor = this.interceptors.get(i);
    				if (interceptor == null) {
    					throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
    				}
    				this.adaptedInterceptors.add(adaptInterceptor(interceptor));
    			}
    		}
    	}
複製代碼

因此看到這裏,就能夠知道咱們自動定義的攔截器最後被添加到了AbstractHandlerMapping中。分析到這裏差很少快結束了。ide

2.1 HandlerExecutionChain

在最後分析以前,要先了解一下這個類HandlerExecutionChain攔截器鏈,這是由handle與一系列的攔截器組成的,也就是咱們自定義的攔截器會被放入這個類中,進行執行,話很少說,直接debug。post

public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	private final Object handler;

	@Nullable
	private HandlerInterceptor[] interceptors;

	@Nullable
	private List<HandlerInterceptor> interceptorList;
	private int interceptorIndex = -1;
	}
複製代碼

org.springframework.web.servlet.DispatcherServlet#doDispatch中打上斷點,請求http://localhost:8080/test,

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 獲取HandlerExecutionChain,核心地方,其餘地方先略過
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

                // 執行攔截器中的preHandle方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// handle真正執行
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				applyDefaultViewName(processedRequest, mv);

                 // 執行攔截器中的postHandle方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}

			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}



	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}
複製代碼

咱們發現返回HandlerExecutionChain的方法getHandler(processedRequest),咱們知道HandlerMapping接口中只有一個方法返回HandlerExecutionChain,而它的實現類剛好是咱們上面分析的AbstractHandlerMapping

public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
複製代碼

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	    // 獲取handle,有興趣的能夠深刻了解這裏
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

        // 本文的關注點,獲取HandlerExecutionChain
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

        // 獲取請求路徑,好比咱們的URL是localhost:8080/test 這裏獲得的lookupPath就是/test
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		    // 遍歷攔截器,判斷是否是MappedInterceptor,若是是的話,則判斷路徑是否知足自定義的路徑
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				// MappedInterceptor也就是咱們自定義的攔截器,而後將路徑/test與MappedInterceptor裏面的excludePatterns和includePatterns進行匹配
				
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}
複製代碼

org.springframework.web.servlet.handler.MappedInterceptor#matches(String ,PathMatcher):

public final class MappedInterceptor implements HandlerInterceptor {
	@Nullable
	private final String[] includePatterns;

	@Nullable
	private final String[] excludePatterns;

	private final HandlerInterceptor interceptor;

	@Nullable
	private PathMatcher pathMatcher;

    // 由於excludePatterns數組裏麪包含"/test",因此不匹配,返回false,這個請求中的攔截器連中就沒有咱們自定義的HelloInterceptor
	public boolean matches(String lookupPath, PathMatcher pathMatcher) {
		PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher);
		if (!ObjectUtils.isEmpty(this.excludePatterns)) {
			for (String pattern : this.excludePatterns) {
				if (pathMatcherToUse.match(pattern, lookupPath)) {
					return false;
				}
			}
		}
		if (ObjectUtils.isEmpty(this.includePatterns)) {
			return true;
		}
		for (String pattern : this.includePatterns) {
			if (pathMatcherToUse.match(pattern, lookupPath)) {
				return true;
			}
		}
		return false;
	}
}
複製代碼

到此攔截器的原理就介紹完了,若是文章有錯誤或者你有什麼疑問,請留言或者經過郵箱聯繫我creazycoder@sina.com

相關文章
相關標籤/搜索