先說結論
在注冊攔截器時,可以通過 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);
}
}