Analysis of the problem that the custom interceptor cannot intercept zuul requests

1 process

1. Create a custom interceptor class MyInterceptor

 

2. Register interceptor

@Configuration
public class WebConf extends WebMvcConfigurerAdapter {

    @Autowired
    MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

3. Call an interface, which is forwarded by zuul. It is found that the request has not passed the custom interceptor created above

4. Through the forum to find information, we found that we need to extend the extension point of spring to add the custom interceptor

@Configuration
public class ZuulHandlerBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {

        if (bean instanceof ZuulHandlerMapping) {

            ZuulHandlerMapping zuulHandlerMapping = (ZuulHandlerMapping) bean;
            zuulHandlerMapping.setInterceptors(myInterceptor);
        }
        return super.postProcessAfterInstantiation(bean, beanName);
    }
}

 

2. Cause analysis

zuul Filter was originally thought to implement the Filter interface of tomcat, but later it was found that it was not. zuul Filter is a set of Filter specifications implemented by zuul itself. Zuul has its own zuul controller, which can be considered as a common controller. The process from request to zuul is the same as that from request to other controllers, which needs to go through tomcatFilter and spring mvc. Then we go through step 2 above and register the custom Filter successfully. Why is the request to zuul not intercepted by the interceptor? Let's analyze the reasons.

 

Analysis of mvc request forwarding process

spring mvc is used in the project, because zuul is a controller, so the request to zuul is also forwarded through the dispatcher servlet of mvc (other materials can be searched by yourself if the overall process of mvc request flow is not known). Next, we analyze the doDispatch method of DispatcherServlet:

Source code analysis of dispatcher Servlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
			//Find handler according to request
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
			//Find the handlerAdapter according to the handler
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    
			//Execute the preprocessor of the corresponding Interceptor
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
			//HandlerAdapter uses handler to process requests
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            . . . 
        }
    }

The key steps have been annotated in the code.

The key is how to find the HandlerExecutionChain mappedHandler

Well, we can have a look this.getHandler(processedRequest); method

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Iterator var2 = this.handlerMappings.iterator();

        HandlerExecutionChain handler;
        do {
            if (!var2.hasNext()) {
                return null;
            }

            HandlerMapping hm = (HandlerMapping)var2.next();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
            }

            handler = hm.getHandler(request);
        } while(handler == null);

        return handler;
    }

Here is to search the processor by traversing the handlerMappings. The key here is that the search handler traverses all the handlerMappings in order, stops and returns immediately after finding one, and does not traverse the subsequent handlerMapping again, so the order of handlerMapping here is very important.

Let's analyze handler again= hm.getHandler (request); how this line of code implements the lookup handler. Here is the logic of HandlerMapping

The figure below is the implementation class inheritance diagram of HandlerMapping interface for reference

The function of HandlerMapping is to find the Handler and Interceptors according to the request

The following figure is the inheritance diagram of ZuulHandlerMapping

getHandler(request) method of ZuulHandlerMapping and its parent class AB stractHandlerMapping.getHandler Method, the source code is as follows
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = this.getHandlerInternal(request);
        if (handler == null) {
            handler = this.getDefaultHandler();
        }

        if (handler == null) {
            return null;
        } else {
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.getApplicationContext().getBean(handlerName);
            }

            HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
            if (CorsUtils.isCorsRequest(request)) {
                CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
                CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
                CorsConfiguration config = globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig;
                executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
            }

            return executionChain;
        }
    }

The function of HandlerMapping is to find the Handler and Interceptors according to the request. The process of getting the Handler is passed to the subclass through the template method getHandlerInternal.

The implementation of getHandler method is divided into two parts. gethandlerExecutionChain used to find the Handler before. gethandlerExecutionChain method is used to add interceptors to the execution chain.

this.getHandlerExecutionChain(handler, request); source code

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
        HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		//This is the breakthrough point of the interceptor
        Iterator var5 = this.adaptedInterceptors.iterator();

        while(var5.hasNext()) {
            HandlerInterceptor interceptor = (HandlerInterceptor)var5.next();
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            } else {
                chain.addInterceptor(interceptor);
            }
        }

        return chain;
    }

The purpose of this method is to add hm's adaptedInterceptors to the execution chain and return them.

In AbstractHandlerMapping, there are two attributes of List type (there were three in the original, among which mappedInterceptors were deleted in the new version): interceptors and adaptedInterceptors. Their functions are as follows:

interceptors: this attribute will not be used directly, but will be allocated to mappedInterceptors and adaptedInterceptors by type through initInterceptors method;

adaptedInterceptors: this kind of interceptors do not need to match, and will be added to the HandlerExecutionChain in getHandler.

 

After analyzing this, we can find that each HandlerMapping has its own two interceptor properties of List type,

While the HandlerMapping used by the interface decorated by @ RequestMapping is RestRpcRequestMappingHandlerMapping, and the order is 0
The order of ZuulHandlerMapping is - 200, so the priority of ZuulHandlerMapping is higher. If a url is configured in a controller through @ RequestMapping and configured in ZuulHandlerMapping at the same time, the request of the url will only enter zuul.


ZuulHandlerMapping implements ApplicationObjectSupport. The setApplicationContext method of ApplicationObjectSupport is as follows

public final void setApplicationContext(ApplicationContext context) throws BeansException {
        if (context == null && !this.isContextRequired()) {
            this.applicationContext = null;
            this.messageSourceAccessor = null;
        } else if (this.applicationContext == null) {
            if (!this.requiredContextClass().isInstance(context)) {
                throw new ApplicationContextException("Invalid application context: needs to be of type [" + this.requiredContextClass().getName() + "]");
            }

            this.applicationContext = context;
            this.messageSourceAccessor = new MessageSourceAccessor(context);
			//Initialize context
            this.initApplicationContext(context);
        } else if (this.applicationContext != context) {
            throw new ApplicationContextException("Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]");
        }

    }



In response to the previous article, we implement WebMvcConfigurerAdapter and rewrite addInterceptors to register the custom interceptor. Through the break point, we find that the custom interceptor is only added to RestRpcRequestMappingHandlerMapping instead of ZuulHandlerMapping. Therefore, the request routed to zuul is not added to the custom interceptor Intercept. If you want the custom interceptor to work on requests to zuul, you need to register the custom interceptor with ZuulHandlerMapping as well.


3. Summary

Each request will find the corresponding HandlerMapping through the dispatcher servlet, and HandlerMapping will get the corresponding interceptor and handler. When traversing HandlerMapping, it is carried out according to its order sequence. ZuulHandlerMapping has a higher priority. If the execution chain is obtained through ZuulHandlerMapping, its corresponding Interceptors and handlers will be executed. Zuul's The handler is ZuulController. ZuulController will hand over the request to ZuulServlet. ZuulServlet is the real zuul life cycle proposed by zuul official documents.

 

 

 

 

Keywords: Spring Tomcat Attribute

Added by pramodv on Sun, 14 Jun 2020 08:19:44 +0300