The most complete summary of the Interceptor of the Spring MVC series (please delete the content in brackets for reprinting)

Understanding Interceptor Interceptor

The Interceptor in Spring MVC is equivalent to the Filter in the Servlet specification. It intercepts the execution of the processor. Because it is a global behavior, it is often used for some general functions, such as request log printing, permission control, etc.

Then take out the diagram of the Spring MVC DispatcherServlet request processing flow. If you don't understand, you can refer to the previous article "Comprehensively understand Spring MVC in 5 minutes" . Spring MVC DispatcherServletWhen the request initiated by the browser reaches the Servlet container, DispatcherServlet first obtains the processor according to the processor mapper HandlerMapping. At this time, it obtains a processor execution chain including the processor and the interceptor. Before the processor is executed, the interceptor will be executed first. device.

In the absence of interceptors, the process of DispatcherServlet processing requests can be simplified as follows. insert image description here
After adding an interceptor for login check, the process of DispatcherServlet request processing can be simplified as follows.
insert image description here

Interceptor Interceptor definition

In fact, the execution flow of the interceptor is far more complex than the simplified flow chart of the above-mentioned DispatcherServelt. It can be executed not only before the processor, but also after the processor. First look at the definition of Interceptor in Spring MVC.

public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
							@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
								 @Nullable Exception ex) throws Exception {
	}	
}

The interceptor is represented by the interface HandlerInterceptor in Spring MVC. This interface contains three methods: preHandle, postHandle, and afterCompletion. The handler parameter of these three methods indicates the processor. Usually, it can indicate that we use the annotation @Controller to define controller.

Continue to refine the above flow chart.

insert image description hereThe specific implementation process of the three methods is as follows.

  • preHandle: Executed before the handler is executed. If false is returned, the handler, interceptor postHandle method, view rendering, etc. will be skipped, and the interceptor afterCompletion method will be executed directly.
  • postHandle: After the processor is executed, it is executed before the view is rendered. If the processor throws an exception, this method will be skipped and the interceptor afterCompletion method will be executed directly.
  • afterCompletion: Executed after the view is rendered, regardless of whether the processor throws an exception, this method will be executed.

Note : Since the separation of the front and back ends, the processor method in Spring MVC usually does not return the view after execution, but returns an object representing json or xml. If the return value type of the @Controller method is ResponseEntity or @ResponseBody annotation, At this point, once the processor method is executed, Spring will use HandlerMethodReturnValueHandler to process the return value. Specifically, the return value will be converted to json or xml, and then written to the response. View rendering will not be performed later, and there will be no chance at this postHandletime Modify the content of the response body.

If you need to change the response content, you can define a class that implements the ResponseBodyAdvice interface, and then define this class directly to requestResponseBodyAdvice in RequestMappingHandlerAdapter or add it to RequestMappingHandlerAdapter through the @ControllerAdvice annotation.

Interceptor use and configuration

To use an interceptor, you need to implement the HandlerInterceptor interface. In order to avoid implementing all the methods of this interface, Spring 5 provided an abstract implementation of HandlerInterceptorAdapter before. After the new features of the default method of the Java 8 interface appear, we can directly implement the HandlerInterceptor interface.
Examples are as follows.

public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求来了");
        return true;
    }
}

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("已登录");
        return true;
    }
}

There are usually three ways to configure Spring, namely traditional xml, the latest annotation configuration, and API configuration, and interceptors are no exception.

xml file configuration

The xml configuration method is as follows.

    <mvc:interceptors >
        <bean class="com.zzuhkp.mvc.interceptor.LogInterceptor"/>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/login"/>
            <bean class="com.zzuhkp.mvc.interceptor.LoginInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

  • bean : The interceptor bean under the mvc:interceptors tag will be applied to all processors.
  • mvc:interceptor : The subtags under this tag can specify which request paths the interceptor applies to.
    • mvc:mapping : Specify the request path for processing.
    • mvc:exclude-mapping : Specifies the excluded request path.
    • bean : Specifies the interceptor bean to apply to the given path.

Annotation configuration

For annotation configuration, MappedInterceptor needs to be configured as a Spring bean, and the annotation configuration equivalent to the above xml configuration is as follows.

@Configuration
public class MvcConfig {

    @Bean
    public MappedInterceptor logInterceptor() {
        return new MappedInterceptor(null, new LoginInterceptor());
    }

    @Bean
    public MappedInterceptor loginInterceptor() {
        return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
    }
}

API configuration

The interceptor is closely integrated with the Spring MVC environment, and its scope is usually global, so it is recommended to use this configuration in most cases.

The API configuration corresponding to the xml configuration is as follows.

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

Here, annotations are added to the configuration class @EnableWebMvcto enable certain features in Spring MVC, and then the addInterceptors method in the WebMvcConfigurer interface can be implemented to add interceptors to Spring MVC. If you use spring-boot-starter-web, you no longer need to manually add @EnableWebMvcannotations.

Interceptor Interceptor Execution Order

Normally, we don't need to care about the execution order of multiple interceptors. However, if one interceptor depends on the execution result of another interceptor, then we need to pay attention. The DispatcherServlet request processing flow after using multiple interceptors can be simplified to the following flow chart.
insert image description here
The execution order of multiple interceptor methods is as follows.

  1. preHandle is executed sequentially in the order of interceptors. If any call returns false, jump directly to the interceptor's afterCompletion execution.
  2. postHandle is executed in reverse order of the interceptors, which means that the subsequent interceptors execute postHandle first.
  3. afterCompletion is also executed in reverse order of the interceptors, and the latter interceptors execute afterCompletion first.

So how is the order of interceptors specified?

  • For xml configuration, Spring will record the order of bean declarations, and the interceptors declared first will come first.
  • @OrderFor annotation configuration, since the order cannot be guaranteed by reading methods through reflection, it is necessary to add annotations to the method to specify the declaration order of beans.
  • Corresponding to the API configuration, the order of interceptors is not completely consistent with the order of adding them. In order to control the sequence, a custom interceptor implementation Orderedinterface is required.

An example of the specified order of annotation configuration is as follows.

@Configuration
public class MvcConfig {

    @Order(2)
    @Bean
    public MappedInterceptor loginInterceptor() {
        return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
    }
    
    @Order(1)
    @Bean
    public MappedInterceptor logInterceptor() {
        return new MappedInterceptor(null, new LoginInterceptor());
    }
    
}

At this time, although the login interceptor is written in front, because the value specified by the @Order annotation is larger, it will be ranked behind the log interceptor.

An example of specifying the order of the API configuration is as follows.

public class LoginInterceptor implements HandlerInterceptor, Ordered {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("已登录");
        return true;
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

public class LogInterceptor implements HandlerInterceptor, Ordered {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求来了");
        return true;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

The sort number specified by LogInterceptor is smaller than that of LoginInterceptor, so LogInterceptor will be sorted first.

Interceptor Principle Analysis of Interceptor

The code for DispatcherServlet to process requests is located in DispatcherServlet#doDispatchthe method, and the simplified code for processors and interceptors is as follows.

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;
			
			    // 从 HandlerMapping 获取处理器链
				mappedHandler = getHandler(processedRequest);
				// 处理器适配
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				// 拦截器 preHandle 执行
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				// 处理器执行
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				// 拦截器 postHandle 执行
				mappedHandler.applyPostHandle(processedRequest, response, mv);
				
			// 视图渲染
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		} catch (Exception ex) {
		  // 拦截器 afterCompletion 执行
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		} catch (Throwable err) {
			// 拦截器 afterCompletion 执行
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
	}

It can be seen that the overall process is consistent with our previous description. Take the interceptor pre-execution preHandle as an example to see how the processor chain calls the interceptor method.

public class HandlerExecutionChain {

	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)) {
					// 返回 false 则直接执行 afterCompletion
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}
}

After the processor chain gets the list of interceptors, it calls the preHandle method of the interceptors in order, and if it returns false, it jumps to afterCompletion for execution. Where does the list of interceptors in the processor chain come from? Continuing to trace the method of obtaining the processor chain DispatcherServlet#getHandler, we can find that the core code of obtaining the processor chain is as follows.

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {

	protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		  // 拦截器添加到处理器链
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			} else {
				chain.addInterceptor(interceptor);
			}
		}
		return chain;
	}
}

After Spring creates the handler chain HandlerExecutionChain, the interceptors in the interceptor list adaptedInterceptors in AbstractHandlerMapping are added to the processor chain, so where do the interceptors in the interceptor list in AbstractHandlerMapping come from?

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
		implements HandlerMapping, Ordered, BeanNameAware {

	private final List<Object> interceptors = new ArrayList<>();

	private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();

	@Override
	protected void initApplicationContext() throws BeansException {
		extendInterceptors(this.interceptors);
		detectMappedInterceptors(this.adaptedInterceptors);
		initInterceptors();
	}

    // 从容器中获取拦截器
	protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
		mappedInterceptors.addAll(
				BeanFactoryUtils.beansOfTypeIncludingAncestors(
						obtainApplicationContext(), MappedInterceptor.class, true, false).values());
	}

	// 拦截器适配
	protected void initInterceptors() {
		if (!this.interceptors.isEmpty()) {
			for (int i = 0; i < this.interceptors.size(); i++) {
				Object interceptor = this.interceptors.get(i);
				if (interceptor == null) {
					throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
				}
				this.adaptedInterceptors.add(adaptInterceptor(interceptor));
			}
		}
	}		
}

Various HandlerMapping implementations inherit AbstractHandlerMapping. When HandlerMapping is created by the container, it will call back #initApplicationContexta method. When this method is called back, it will find an interceptor of type MappedInterceptor from the container, and then adapt the interceptor. If @EnableWebMvc is used in Spring MVC, when the HandlerMapping bean is created, the callback WebMvcConfigurer#addInterceptorsmethod will directly set the interceptor to the interceptors in AbstractHandlerMapping.

Summarize

The process related to the entire interceptor of Spring MVC is summarized as follows.

  1. HandlerMapping is instantiated and initialized by the container.
    1. During initialization, by default, an interceptor of type MappedInterceptor is searched from the container and added to the list of interceptors in HandlerMapping. This default behavior supports xml and annotation configuration interceptors.
    2. After using the @EnableWebMvc annotation, Spring creates a HandlerMapping bean through @Bean, and after instantiation, the callback WebMvcConfigurer#addInterceptorssets the interceptor to the interceptor list in HandlerMapping in advance. This behavior supports the API configuration interceptor.
  2. When the client initiates a request, DispatcherServlet uses HandlerMapping to find the handler execution chain, and adds the interceptor in HandlerMapping to the list of interceptors in the handler execution chain HandlerExecutionChain.
  3. DispatcherServlet calls the callback methods in the interceptors in sequence according to the order of the interceptors.


 

Guess you like

Origin blog.csdn.net/qq_43985303/article/details/131719636