Six major differences between filters and interceptors

        I usually think that knowledge points are simple, but I usually don’t pay too much attention to details. Once asked by others, I can’t explain why. It’s really something that will be dismissed as soon as you look at it. Let’s take everyone together to distinguish filters and interceptors based on practice~

Popular understanding :
(1) Filter: When you have a bunch of things, you only want to select something that meets your requirements. The tool for defining these requirements is the filter. (Understanding: Just pick a B from a bunch of letters)
(2) Interceptor: When a process is in progress, you want to interfere with its progress or even terminate it. This is what the interceptor does. (Understanding: It’s just a bunch of letters, intervene in them, less of them pass the verification, and do other things by the way)

1. Preparation work

        We configure the filters and interceptors in the project as follows:

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 the Filter interface defines three methods:

(1) 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.

(2) doFilter() : This method will be called for every request in the container, and FilterChain is used to call the next filter.

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

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;


/**
 * @author: tangbingbing
 * @date: 2023/8/7 15:52
 */
@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 (Interceptor AOP idea)

        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.

The interface also defines three methods as follows:

(1) 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.

(2) 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.

(3) 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.

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:05
 */
@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 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.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author: tangbingbing
 * @date: 2023/8/7 16:12
 */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
	}
}

2. Six major differences between filters and interceptors

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).

Here we focus on analyzing filters

In our custom filters, we will implement a doFilter() method, which 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;
}

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

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. .

        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.

3. Different triggering times

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

 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

Create a new controller as follows

@RestController
public class TestController {

	@RequestMapping("/test")
	public String test(){
		//System.out.println("源码环境构建成功...");
		System.out.println("Controller Method...");
		return "源码环境构建成功";
	}
}

During the project startup process, it was discovered that the filter init()method was initialized with the startup of the container.

 Request the Controller interface console as follows

Execution order: Filter processing -> Interceptor pre-processing -> Controller Method... -> Interceptor processing -> Interceptor post-processing

Filters Filtercan be executed multiple times, interceptors Interceptorare only executed once. This is because the filter can act on almost all requests entering the container, while the interceptor will only act on Controllerrequests in the middle or accessing staticresources in the directory.

5. The situation of injecting beans is different

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

Next, let's inject it into both the filter and the interceptor serviceto see what's the difference?

/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
public interface TestService {
	void a();
}
/**
 * @author: tangbingbing
 * @date: 2023/8/7 17:24
 */
@Component
public class TestServiceImpl implements TestService {

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

Inject service in filter

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

The access interface console is as follows

 Inject the service into the interceptor, initiate a request and test it, and an error is reported. After debugging, I found that the injected service is Null.

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

ps: How to solve this problem? keep going down

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(new MyInterceptor()).addPathPatterns("/**");
		registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
	}
}

Access the interface again and check the console. It’s normal.

 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 default execution order of the interceptor is its registration order. It can also be @Ordermanually set and controlled through annotations. The smaller the value, the first it will be executed.

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
}
 @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 前置
Interceptor2 前置
Interceptor 前置
Controller Method...
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

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 loop was actually reversed, causing the postHandle() and preHandle() methods to be executed in the opposite order.

Guess you like

Origin blog.csdn.net/qq_45443475/article/details/132147949