设置 Spring MVC 拦截器优先级

先说结论

在注册拦截器时,可以通过 order方法设置拦截器优先级

问题产生

在自己的项目中引入监控平台(整合了第三方项目),但是监控平台的监控拦截器,并没有监控到我本项目中拦截器的行为,通过调试发现,监控平台的拦截器在自己项目的拦截器之后才执行,因此无法监控到我的项目中拦截器所做的逻辑。

说明

项目A(我的项目,引入了项目B):项目拦截器配置(用户身份验证)

@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Configuration
public class AppWebMvcConfigurer implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/v?/**");
    }
}

项目B(监控平台) :监控平台拦截器的配置(监控一些用户访问的日志信息,比如耗时等)。由于是第三方组件中的代码,这部分代码我是不可修改的

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    public InterceptorConfig() {
    }

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MonitorInterceptor())
            	.addPathPatterns(new String[]{"/**"});
    }
}

原理

通过 debug 查看 DispatcherServlet 中获取的拦截器的列表顺序,发现监控拦截器在项目拦截器之后,因此执行的时候顺序不正确,相当于监控是范围是在经过我的拦截器之后才开始的

graph TD A("DispatcherServlet.doDispath()") --> B("mappedHandler.appPreHandle()") B --> C("获取拦截器列表,遍历执行拦截器的逻辑(由于代码执行顺序问题,不在同一项目中,AuthInterceptor先注册了)") C --> D("1.先取出AuthInterceptor拦截器执行") C --> E("2.再取出MonitorInterceptor拦截器执行")
// HandlerExecutionChain.java

/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
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;
}

解决办法

由于无法修改监控平台组件的代码,可以在自己的项目中,通过注册时添加order,来设置拦截器列表中的顺序,将自己的拦截器 order设置为最低优先级,从而保证了监控平台的拦截器先执行。

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(appSecurityInterceptor)
        .addPathPatterns("/v?/**")
        .order(Ordered.LOWEST_PRECEDENCE);
}

查看源码

查看上面的拦截器配置类中的 添加拦截器的方法的参数 register —— InterceptorRegistry

InterceptorRegistry.addInterceptor() 添加拦截器方法,将传入的 HandlerInterceptor 包装成了一个 InterceptorRegistration ,添加到 registrations 中,并将包装好的对象返回

/**
 * Helps with configuring a list of mapped interceptors.
 *
 * @author Rossen Stoyanchev
 * @author Keith Donald
 * @since 3.1
 */
public class InterceptorRegistry {

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


	/**
	 * Adds the provided {@link HandlerInterceptor}.
	 * @param interceptor the interceptor to add
	 * @return an {@link InterceptorRegistration} that allows you optionally configure the
	 * registered interceptor further for example adding URL patterns it should apply to.
	 */
	public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
		InterceptorRegistration registration = new InterceptorRegistration(interceptor);
		this.registrations.add(registration);
		return registration;
	}
    
    //...
}

查看 InterceptorRegistration 代码,属性中有 order字段,并提供了修改order的方法,说明 InterceptorRegistration 是可排序的:

/**
 * Assists with the creation of a {@link MappedInterceptor}.
 *
 * @author Rossen Stoyanchev
 * @author Keith Donald
 * @since 3.1
 */
public class InterceptorRegistration {

	private final HandlerInterceptor interceptor;

	private final List<String> includePatterns = new ArrayList<>();

	private final List<String> excludePatterns = new ArrayList<>();

	@Nullable
	private PathMatcher pathMatcher;

	private int order = 0; // 优先级默认是 0
    
    // ...

    /**
	 * Specify an order position to be used. Default is 0.
	 * @since 4.3.23
	 */
	public InterceptorRegistration order(int order){
		this.order = order;
		return this;
	}
    
    // ...
}

InterceptorRegistry.getInterceptors() ,对返回的结果 InterceptorRegistration集合 做了一个排序,并做了类型装换。如果没有设置过 order,则order都是0,就按直接代码中注册的顺序来了

// InterceptorRegistry.getInterceptors()代码,有一个sorted过程
/**
 * Return all registered interceptors.
 */
protected List<Object> getInterceptors() {
    return this.registrations.stream()
        .sorted(INTERCEPTOR_ORDER_COMPARATOR)
        .map(InterceptorRegistration::getInterceptor)
        .collect(Collectors.toList());
}

/**
 * Build the underlying interceptor. If URL patterns are provided, the returned
 * type is {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
 */
protected Object getInterceptor() {
    if (this.includePatterns.isEmpty() && this.excludePatterns.isEmpty()) {
        return this.interceptor;
    }

    String[] include = StringUtils.toStringArray(this.includePatterns);
    String[] exclude = StringUtils.toStringArray(this.excludePatterns);
    MappedInterceptor mappedInterceptor = new MappedInterceptor(include, exclude, this.interceptor);
    if (this.pathMatcher != null) {
        mappedInterceptor.setPathMatcher(this.pathMatcher);
    }
    return mappedInterceptor;
}

因此,只要在注册拦截器时,设置这个order,就可以对拦截器进行排序了

总结

网上很多设置拦截器优先级的文章,都是说将注册拦截器的代码按从上到下手动注册(原理还是上面的拦截器根据集合中的顺序来执行),对于引入其他项目中的拦截器,这种方式根本无法控制拦截器的顺序。

可以通过 InterceptorRegistration 提供的 order方法在自己的项目中设置拦截器的优先级。直接在注册的时候 .order(xxx) 就可以了。

@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Configuration
public class AppWebMvcConfigurer implements WebMvcConfigurer {

    private final AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/v?/**")
            	.order(Ordered.LOWEST_PRECEDENCE);
    }
}
posted @ 2020-10-13 14:40  它山之玉  阅读(5230)  评论(0编辑  收藏  举报