spring mvc的拦截器的那段源代码分析

起源,研究拦截器匹配规则为什么得不到执行

@Configuration("admimWebConfig")
public class WebConfiguration implements WebMvcConfigurer {
 public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getServiceAuthRestInterceptor()).
                addPathPatterns(getIncludePathPatterns());
     
    }
}

可以发现,拦截器是注入到registrations里面

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

通过idea,可以看到registration只有在这个方法里用过,

protected List<Object> getInterceptors() {
        return this.registrations.stream()
                .sorted(INTERCEPTOR_ORDER_COMPARATOR)
                .map(InterceptorRegistration::getInterceptor)
                .collect(Collectors.toList());
    }

然后这个方法是在WebMvcConfigurationSupport这里调用的
维护在这个类的interceptors变量里,而通过idea拦截器发现,也只有下面这个方法有返回也就是使用这个变量

protected final Object[] getInterceptors() {
        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();
    }

上面这个方法是WebMvcConfigurationSupport这个方法调用的

@Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
    // 可以看到,是RequestMappingHandlerMapping的setInterceptors的拦截
//器调用了,也就是说,RequestMappingHandlerMapping维护了所有的拦截器
// 维护在interceptors这个变量中
    mapping.setInterceptors(getInterceptors());
    ...
        return mapping;
    }

而以前分析spring mvc源码的时候,就直到,调用拦截器是在DispatchServlet的doDispatch方法里调用的

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                      ...
// 找到能处理这个请求的handlerMapping
// 这里找到的handlerMapping,已经初始化过了所有的拦截器
// 这里通过handlermapping,获取HandlerExecutionChain,这里会过滤掉不匹配的拦截器
                mappedHandler = getHandler(processedRequest);
    ...
// 这里会调用拦截器
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                         ...            
    }

看调用拦截器的方法
debug发现,在这里的HandlerExecution的interceptors变量已经是过滤后的拦截器了,已经把不匹配的拦截器过滤掉了
那么这个变量interceptors到底是在哪里被赋值了呢?通过idea以及debug流程发下,没有任何地方,给interceptors赋有效的值,HandlerExecution的interceptors的值到底是怎么来的呢?
当时就很奇怪了,为什么呢?那它的值是哪来的呢?
后来通过,头构造HandlerExecution的代码,发现,忽略了一个事情,interceptors是一个集合,可以通过return 获取到这个集合,然后掉哟个add方法来改变值,而不是直接赋值改变值

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }
public HandlerInterceptor[] getInterceptors() {
        if (this.interceptors == null && this.interceptorList != null) {
            this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
        }
        return this.interceptors;
    }

也就是这行代码,过滤掉了拦截器

                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
//这行代码
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        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 executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.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));
// 这是我们请求的路径
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
// 这里进行匹配
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            }
            else {
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }

再看过滤是怎么过滤的
会发现,首先,通过excludeUrl过滤,在excludeUrl中的,则直接过滤掉,也就是不添加这个拦截器(可是很多人会发现,添加到excludeUrl中,然后发现不起作用,这是为什么呢?看最下面的代码
再根据includeUrl过滤,includeUrl为空的,则添加这个过滤器,意思就是,如果不自定义includeUrl,则拦截器所有。
如果includeUrl不为空,则进行匹配拦截

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;
    }

这是具体的匹配代码:AntPathMatcher类,也就是aut模式的匹配,详细的aut匹配方式,百度有很多。
关键看第一行代码

protected boolean doMatch(String pattern, String path, boolean fullMatch,
            @Nullable Map<String, String> uriTemplateVariables) {
// pathSeparator路径分割符是“/”,path如果以“/”分割,而pathSeparator不以“/”分
//割,则直接返回false。也就是说,如果访问swagger-ui.html页面,这里的path
//是/swagger-ui.html,而不管配置的excludeUrl是:**swagger.html还是
//*swagger.html,都将返回false,匹配不到
        if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
            return false;
        }
    ...
        return true;
    }

那么AntPathMatcher匹配模式是怎样的呢?
AntPathMatcher如名使用的ant 的匹配规则,我们先看看吧.

字符wildcard    描述

?         匹配一个字符

*         匹配0个及以上字符(不包括/目录分隔符)

**         匹配0个及以上目录directories

看几个例子:
/*swagger.html 匹配/swagger.html ,*是0个或多个嘛

//swagger.html 也匹配/swagger.html **是0个或多个目录嘛
/
/.png 匹配/webjars/springfox-swagger-ui/images/favicon-32x32.png **匹配多层目录,*匹配多个字符

com/t?st.jsp -            匹配: com/test.jsp , com/tast.jsp , com/txst.jsp
  com/.jsp -             匹配: com文件夹下的全部.jsp文件
  com/
/test.jsp -          匹配: com文件夹和子文件夹下的全部.jsp文件,
  org/springframework/
/.jsp -   匹配: org/springframework文件夹和子文件夹下的全部.jsp文件
  org/**/servlet/bla.jsp -       匹配: org/springframework/servlet/bla.jsp , org/springframework/testing/servlet/bla.jsp , org/servlet/bla.jsp

可以写个例子测试:

 public static void main(String[] args) {
        pathMatcher.setCachePatterns(true);
        pathMatcher.setCaseSensitive(true);
        pathMatcher.setTrimTokens(true);
        pathMatcher.setPathSeparator("/");

        Assert.assertTrue(pathMatcher.match("/**/*swagger.html", "/swagger.html"));
        Assert.assertTrue(pathMatcher.match("/**/*.png", "/webjars/springfox-swagger-ui/images/favicon-32x32.png"));
        Assert.assertTrue(pathMatcher.match("a*", "ab"));
        Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a"));
        Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a"));


        Assert.assertTrue(pathMatcher.match("*", "a"));
        Assert.assertTrue(pathMatcher.match("*/*", "a/a"));
    }

参考:
https://wenchao.ren/2019/01/Spring%E7%9A%84AntPathMatcher%E6%98%AF%E4%B8%AA%E5%A5%BD%E4%B8%9C%E8%A5%BF/

https://www.cnblogs.com/leftthen/p/5212221.html

转载于:https://www.jianshu.com/p/ce3e93670fc8

猜你喜欢

转载自blog.csdn.net/weixin_34247032/article/details/91202647
今日推荐