[Java] The difference between filters and interceptors

Preface

Prepare the environment
We configure both interceptors and filters in the project.

1. Filter

The configuration of the filter is relatively simple. You can directly implement the Filter interface. You can also intercept specific URLs through the @WebFilter annotation. You can see that there are three methods defined in the Filter interface.

  • init(): This method is called when the container starts to initialize the filter. It will only be called once in the entire life cycle of the 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, and FilterChain is used to call the next filter.

  • destroy(): This method is called when the container destroys the filter instance. Generally, resources are destroyed or closed in the method. It will only be called once during the entire life cycle of the filter.

@Component
public class MyFilter implements Filter {
    
    
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    
    

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    

        System.out.println("Filter 处理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    
    

        System.out.println("Filter 后置");
    }
}

2. Interceptor

Interceptors are chain calls. Multiple interceptors can exist in an application at the same time. One request can also trigger multiple interceptors, and the calls of each interceptor will be executed in sequence according to the order in which it is declared.

First, write a simple interceptor processing class. The interception of requests is implemented through HandlerInterceptor. You can see that the HandlerInterceptor interface also defines three methods.

  • preHandle(): This method will be called before the request is processed. Note: If the return value of this method is false, the current request will be deemed to have ended. Not only will its own interceptor become invalid, it will also cause other interceptors to no longer be executed.

  • postHandle(): It will only be executed when the return value of the preHandle() method is true. It will be called after the method call in the Controller and before the DispatcherServlet returns to the rendered view. What's interesting is that the postHandle() method is called in the opposite order to preHandle(). The preHandle() method of the interceptor declared first is executed first, while the postHandle() method is executed later.

  • afterCompletion(): It will only be executed when the return value of the preHandle() method is true. After the entire request is completed, DispatcherServlet is executed after rendering the corresponding view.

@Component
public class MyInterceptor implements HandlerInterceptor {
    
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    

        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    

        System.out.println("Interceptor 处理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    

        System.out.println("Interceptor 后置");
    }
}

Register the customized interceptor processing class, and set the URLs that need to be intercepted or excluded through attributes such as addPathPatterns and excludePathPatterns.

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

3. We are different

Both filters and interceptors embody the programming ideas of AOP and can implement functions such as logging and login authentication. However, there are many differences between the two, which will be explained one by one below.

3.1. Different implementation principles

The underlying implementation methods of filters and interceptors are quite different. Filters are based on function callbacks, while interceptors are implemented based on Java's reflection mechanism (dynamic proxy).

The focus here is on filters!

In our custom filters, we will implement a doFilter() method. This method has a FilterChain parameter, but in fact it is a callback interface. ApplicationFilterChain is its implementation class. This implementation class also has a doFilter() method inside which is the callback method.

public interface FilterChain {
    
    
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

Insert image description here

We can get our customized xxxFilter class in ApplicationFilterChain, call each customized xxxFilter 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) {
    
    
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    
    
    if (pos < n) {
    
    
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

Each xxxFilter will first execute its own doFilter() filtering logic, and finally execute filterChain.doFilter(servletRequest, servletResponse) before the end of the execution, which is to call back the doFilter() method of ApplicationFilterChain, thereby cyclically executing the function callback.

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    

    filterChain.doFilter(servletRequest, servletResponse);
}

3.2. Different scope of use

We see that the filter implements the javax.servlet.Filter interface, and this interface is defined in the Servlet specification, which means that the use of the filter depends on containers such as Tomcat, causing it to only be used in web programs. .
Insert image description here

The Interceptor is a Spring component and is managed by the Spring container. It 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 Application, Swing and other programs.

Insert image description here

3.3. Different triggering timings

The triggering timing of filters and interceptors is also different. Let’s look at the picture below.

Insert image description here

Filter Filter is preprocessed after the request enters the container, but before entering the servlet. The request ends after the servlet is processed.

The interceptor is preprocessed after the request enters the servlet and before entering the Controller. The request ends after the corresponding view is rendered in the Controller.

4. The scope of intercepted requests is different.

We have configured filters and interceptors at the same time above, and then built a Controller to receive the request and test it.

@Controller
@RequestMapping()
public class Test {
    
    

    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
    
    
        System.out.println("我是controller");
        return null;
    }
}

During the project startup process, it was discovered that the filter's init() method was initialized as the container started.
Insert image description here

At this time, the browser sends a request, and F12 sees that there are actually two requests, one is our customized Controller request, and the other is a request to access static icon resources.
Insert image description here

See the console print log as follows:

Execution order: Filter processing -> Interceptor pre-processing -> I am the controller -> Interceptor processing -> Interceptor post-processing

  • Filter Processing
  • Interceptor front
  • Interceptor is processing
  • Interceptor post
  • Filter Processing

The filter Filter is executed twice, and the interceptor Interceptor is executed only once. This is because the filter can act on almost all requests entering the container, while the interceptor will only act on requests in the Controller or requests to access resources in the static directory.

5. The situation of injecting beans is different

In actual business scenarios, when applying filters or interceptors, it is inevitable that some services will be introduced to process business logic.

Next we inject service into both the filter and interceptor to see what the difference is?

@Component
public class TestServiceImpl implements TestService {
    
    

    @Override
    public void a() {
    
    
        System.out.println("我是方法A");
    }
}

Inject service into the filter and initiate a request to test. The log prints out "I am method A" normally.

@Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    

        System.out.println("Filter 处理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }
  • Filter Processing
  • I am method A
  • Interceptor front
  • I am controller
  • Interceptor is processing
  • Interceptor post

Inject the service into the interceptor, initiate a request and test it, but an error is reported. After debugging, I found out that the injected service is Null?
Insert image description here

This is due to a problem caused by the loading order. The interceptor is loaded before springcontext, and the beans are managed by spring.

Interceptor: I'm going to the bridal chamber today; Spring: Brother, don't make trouble, I haven't given birth to your wife yet!

The solution is also very simple. Before registering the interceptor, we first inject the Interceptor manually. Note: What is registered in registry.addInterceptor() is the getMyInterceptor() instance.

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
    

    @Bean
    public MyInterceptor getMyInterceptor(){
    
    
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

6. Control execution order is different

In the actual development process, multiple filters or interceptors may exist at the same time. However, sometimes we want a certain filter or interceptor to be executed first, which involves their execution order.

The filter uses the @Order annotation to control the order of execution, and @Order controls the level of the filter. The smaller the value, the higher the level, and the higher the level, the earlier it will be executed.

@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);
    }

Seeing the output results, we found that the preHandle() method of the interceptor declared first is executed first, while the postHandle() method is executed later.

The order in which the postHandle() method is called is actually opposite to that of preHandle()! If the execution sequence is strictly required in actual development, special attention needs to be paid to this point.

  • Interceptor1 front
  • Interceptor2 front
  • Interceptor front
  • I am controller
  • Interceptor is processing
  • Interceptor2 is processing
  • Interceptor1 is processing
  • Interceptor post
  • After Interceptor2 processing
  • After Interceptor1 processing

So why is this happening? To get the answer, we can only look at the source code. We need to know that all requests in the controller must be routed through the core component DispatcherServlet, and its doDispatch() method will be executed, and the interceptor postHandle() and preHandle() methods are called in it. of.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
    
        try {
    
    
         ...........
            try {
    
    
           
                // 获取可以执行当前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;
                    }
                }
                // 注意: 执行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    
    
                    return;
                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
    
    
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

If you look at how the two methods applyPreHandle() and applyPostHandle() are called, you will understand why the execution order of postHandle() and preHandle() is reversed.

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 was found that when calling the interceptor array HandlerInterceptor[] in the two methods, the order of the loops was actually reversed. . . , causing the postHandle() and preHandle() methods to be executed in the opposite order.

Summarize

I believe that most people can use filters and interceptors proficiently, but you still need to know more about the difference between the two. Otherwise, if you use them improperly during development, strange problems will occur from time to time. The above content is relatively simple, and novices can learn from experienced birds. Review, I hope you will actively add to any omissions. If you understand anything wrong, please feel free to give me some advice.

Guess you like

Origin blog.csdn.net/u011397981/article/details/132813345
Recommended