設置 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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM