What is the difference between a filter and an interceptor? Next, combined with practice, more intuitively feel the difference between the two?
Prepare environment
We configure both interceptors and filters in the project.
1. Filter
The Filter configuration is relatively simple. You can directly implement the Filter interface or intercept specific URL s through the @ WebFilter annotation. You can see that three methods are defined in the Filter interface.
-
init(): this method is called when the container starts initializing the Filter. It will only be called once in the whole life cycle of Filter. Note: this method must be executed successfully, otherwise the Filter will not work.
-
doFilter(): this method will be called for every request in the container. FilterChain , is used to call the next Filter , Filter.
-
destroy(): this method is called when the container destroys the Filter instance. Generally, resources are destroyed or closed in the method, and it will only be called once in the whole life cycle of the Filter
@Component public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Filter Front"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter Processing"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("Filter Post"); } }


2. Interceptor
Interceptors are chain calls. Multiple interceptors can exist in an application at the same time, and multiple interceptors can be triggered by a request, and the calls of each Interceptor will be executed in turn according to its declaration order.
First, write a simple interceptor processing class. The request interception is realized through HandlerInterceptor. You can see that three methods are also defined in the HandlerInterceptor interface.
-
preHandle(): this method will be called before the request is processed. java training Note: if the return value of this method is false, it will be regarded as the end of the current request, which will not only invalidate its own interceptor, but also cause other interceptors to no longer execute.
-
postHandle(): it will be executed only when the return value of the pre handle() method is true. It will be called after the method call in the Controller and before the DispatcherServlet returns to the rendered view. Interestingly, the order in which the posthandle () method is called is opposite to that of the pre handle (). The interceptor declared first executes the pre handle () method first, while the posthandle () method executes later.
-
afterCompletion(): it will be executed only when the return value of the pre handle() method is true. After the entire request is completed, the dispatcher servlet renders the corresponding view and executes.
@Component public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor Front"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Interceptor Processing"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("Interceptor Post"); } }


Register the customized interceptor processing class, and set the URL to be intercepted or excluded through addPathPatterns, excludePathPatterns and other properties.
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); } }


We are different
Both filters and interceptors embody the programming idea of AOP, and can realize functions such as logging and login authentication, but there are many differences between them, which will be explained one by one.
1. Different implementation principles
The underlying implementation methods of filter and interceptor are quite different. Filter {is based on function callback, and interceptor} is implemented based on Java reflection mechanism (dynamic agent).
Here's the key to the filter!
In our custom filters, we will implement a , doFilter() method, which has a FilterChain , parameter. In fact, it is a callback interface. ApplicationFilterChain is its implementation class. There is also a doFilter() method inside this implementation class, which is a callback method.
public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; }


In ApplicationFilterChain, we can get our custom xxfilter class, call each custom xxfilter filter in its internal callback method doFilter(), and execute the {doFilter() method.
public final class ApplicationFilterChain implements FilterChain { @Override public void doFilter(ServletRequest request, ServletResponse response) { ...//ellipsis internalDoFilter(request,response); } private void internalDoFilter(ServletRequest request, ServletResponse response){ if (pos < n) { //Get the pos filter ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); ... filter.doFilter(request, response, this); } } }


Each xxxFilter will first execute its own filtering logic of {doFilter(), and finally execute filterchain. Before the end of execution Dofilter (ServletRequest, servletresponse), that is, call back the dofilter () method of ApplicationFilterChain to implement the function callback in a loop.
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest, servletResponse); }


2. Different scope of use
We see that the filter implements javax Servlet. Filter interface, which is defined in the Servlet specification, that is, the use of filter depends on containers such as Tomcat, so it can only be used in web programs.
The interceptor, which is a Spring component and managed by the Spring container, does not rely on containers such as Tomcat and can be used alone. It can be used not only in web programs, but also in applications, Swing and other programs.
3. Different trigger timing
The trigger timing of filters and interceptors is also different. Let's look at the figure below.
The Filter performs preprocessing after the request enters the container but before entering the servlet. The request ends after the servlet is processed.
The Interceptor interceptor preprocesses the request after entering the servlet and before entering the Controller. After rendering the corresponding view in the Controller , the request ends.
4. The scope of intercepted requests is different
On the top, we have configured both filters and interceptors, and built a Controller to receive requests for testing.
@Controller @RequestMapping() public class Test { @RequestMapping("/test1") @ResponseBody public String test1(String a) { System.out.println("I am controller"); return null; } }


During the project startup, it is found that the init() method of the filter is initialized with the startup of the container.
At this time, the browser sends a request. F12 sees that there are actually two requests, one is our custom , Controller , request, and the other is the request to access static icon resources.
See the print log of the console as follows:
Execution sequence: Filter processing - > Interceptor before - > I am the controller - > Interceptor processing - > Interceptor after processing
Filter Processing Interceptor Front Interceptor Processing Interceptor Post Filter Processing


The Filter is executed twice and the Interceptor is executed only once. This is because the Filter can work on almost all requests entering the container, while the Interceptor will only work on requests in the Controller or requests to access resources under the static directory.
5. Bean injection is different
In actual business scenarios, when applied to filters or interceptors, it is inevitable to introduce some service s to process business logic.
Next, we inject service s into filters and interceptors respectively to see the difference?
@Component public class TestServiceImpl implements TestService { @Override public void a() { System.out.println("I'm the way A"); } }


Inject A service into the filter, initiate A request to test, and the log normally prints "I am method A".
@Autowired private TestService testService; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter Processing"); testService.a(); filterChain.doFilter(servletRequest, servletResponse); }


Filter Processing I'm the way A Interceptor Front I am controller Interceptor Processing Interceptor Post


Inject a service into the interceptor and initiate a request to test it. Unexpectedly, TM reports an error. debug and find out how the injected service is Null?
This is due to the problem caused by the loading sequence. The interceptor loads before the springcontext, and the Bean is managed by spring.
The solution is also very simple. Before registering the Interceptor, we manually inject the Interceptor. "Note": in registry Addinterceptor () registers an instance of getMyInterceptor().
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public MyInterceptor getMyInterceptor(){ System.out.println("Injected MyInterceptor"); return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); } }


6. Control execution sequence is different
In the actual development process, multiple filters or interceptors exist at the same time. However, sometimes we want a filter or interceptor to be executed first, which involves their execution order.
The filter uses the @ Order annotation to control the execution Order. The filter level is controlled through @ Order. The smaller the value, the higher the level, and the first to execute.
@Order(Ordered.HIGHEST_PRECEDENCE) @Component public class MyFilter2 implements Filter {


The default execution Order of the interceptor is its registration Order. It can also be manually set and controlled through Order. The smaller the value, the earlier it is executed.
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1); registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3); }


When you see the output result, it is found that the interceptor pre handle () method declared first will execute first, while the postHandle() method will execute later.
The order in which the postHandle() method is called is actually the opposite of the pre handle ()! If the execution sequence is strictly required in actual development, this needs special attention.
Interceptor1 Front Interceptor2 Front Interceptor Front I am controller Interceptor Processing Interceptor2 Processing Interceptor1 Processing Interceptor Post Interceptor2 After treatment Interceptor1 After treatment


"Then why?" To get the answer, we can only look at the source code. We need to know that all requests in controller will be routed through the core component DispatcherServlet, which will execute its doDispatch() method, and the interceptor postHandle() and preHandle() method will be invoked in it.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { try { ........... try { // Gets the adapter that can execute the current Handler HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // Note: execute the PreHandle() method in the Interceptor if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Note: execute Handle [including our business logic, which will be detected by Try and catch when an exception is thrown] mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // Note: execute the PostHandle method in Interceptor [cannot be executed when an exception is thrown] mappedHandler.applyPostHandle(processedRequest, response, mv); } } ........... }


Look at how the two methods applyPreHandle() and applyPostHandle() are called, and you can see why the execution order of postHandle() and preHandle() is opposite.
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[i]; if(!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; }


void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } }


It is found that in the two methods, when calling the interceptor array "HandlerInterceptor [], the order of the loop is reversed..., This causes the postHandle() and preHandle() methods to execute in reverse order.