设置 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
中获取的拦截器的列表顺序,发现监控拦截器在项目拦截器之后,因此执行的时候顺序不正确,相当于监控是范围是在经过我的拦截器之后才开始的
// 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);
}
}