13 Spring Web Filters Usage Common Errors Part 1

13 Spring Web Filters Usage Common Errors Part 1 #

Hello, I’m Fu Jian.

As we all know, filters are one of the important standards in Servlet and play an irreplaceable role in unified processing of requests and responses, access log recording, request permission auditing, etc. In Spring programming, we mainly use the @ServletComponentScan and @WebFilter annotations to build filters.

In theory, it sounds simple, as if marking these two annotations will solve all the problems. However, we still encounter various issues such as not working, incorrect order, multiple executions, etc. These problems mostly occur because we underestimate the simplicity and fail to pay enough attention. With increased awareness, most of these issues can be avoided.

Now let’s study two typical cases to further understand the process and principle of filter execution through analysis.

Case 1: @WebFilter filter cannot be autowired #

Let’s say we want to develop a student management system based on Spring Boot. To calculate the time spent on API calls, we can implement a filter as follows:

@WebFilter
@Slf4j
public class TimeCostFilter implements Filter {
    public TimeCostFilter(){
        System.out.println("construct");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("Calculating API call time");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("Execution time(ms): " + time);
    }
}

This filter is annotated with @WebFilter. So in the startup program, we need to add the annotation scanning (@ServletComponentScan) to make it effective. The startup program is as follows:

@SpringBootApplication
@ServletComponentScan
@Slf4j
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        log.info("Started successfully");
    }
}

Then, we provide a StudentController interface for student registration:

@Controller
@Slf4j
public class StudentController {
   
    @PostMapping("/regStudent/{name}")
    @ResponseBody
    public String saveUser(String name) throws Exception {
        System.out.println("User registration successful");
        return "success";
    }
}

After completing the above program, you will find that everything is working as expected. But if one day, we need to output the statistical data recorded by TimeCostFilter to a professional measurement system (ElasticSearch/InfluxDB, etc.), we may add a service class like this:

@Service
public class MetricsService {

    @Autowired
    public TimeCostFilter timeCostFilter;
    // omit other non-key code

}

After completion, you will find that Spring Boot cannot start:

***************************- APPLICATION FAILED TO START-

***************************- #

Description:- #

Field timeCostFilter in com.spring.puzzle.web.filter.example1.MetricsService required a bean of type ‘com.spring.puzzle.web.filter.example1.TimeCostFilter’ that could not be found.

Why is this happening? Since TimeCostFilter is effective and looks like a regular bean, why can’t it be automatically injected?

Case Analysis #

This time, let me tell you the conclusion first, and you can take a few minutes to think about the key points.

In essence, after the filter is annotated with @WebFilter, TimeCostFilter will only be wrapped as a FilterRegistrationBean, and TimeCostFilter itself will only be instantiated as an InnerBean. This means that the TimeCostFilter instance will not be registered as a Bean in the Spring container.

So when we want to autowire TimeCostFilter, it will fail. Knowing this conclusion, we can clarify some key logic with two questions:

  1. What is FilterRegistrationBean? How is it defined?
  2. How is TimeCostFilter instantiated and associated with FilterRegistrationBean?

Let’s first look at the first question: What is FilterRegistrationBean? How is it defined?

Actually, the full name of WebFilter is javax.servlet.annotation.WebFilter, which obviously does not belong to Spring, but is a specification of Servlet. When it is used in a Spring Boot project, Spring Boot uses org.springframework.boot.web.servlet.FilterRegistrationBean to wrap the instance marked with @WebFilter. In terms of implementation, the FilterRegistrationBean#Filter attribute is the instance marked with @WebFilter. We can see this from the screenshot provided earlier.

In addition, when we define a Filter class, what we may think is that we will automatically generate its instance and use the name of the Filter as the name of the Bean to point to it. But if you debug it, you will find that in Spring Boot, the Bean name is correct, but the Bean instance is actually FilterRegistrationBean.

So, how did we originally get this FilterRegistrationBean? We have to trace back to how the @WebFilter annotation is processed. Before getting into the specific parsing, let’s search for the use of @WebFilter directly and see that the WebFilterHandler class uses it and add a breakpoint in doHandle(). Then start debugging, and the execution call stack is as follows:

From the stack trace, we can see that the processing of @WebFilter is done during Spring Boot startup, and the trigger point for this processing is the ServletComponentRegisteringPostProcessor class. It implements the BeanFactoryPostProcessor interface and handles the scanning and processing of @WebFilter, @WebListener, and @WebServlet. The processing of @WebFilter uses the WebFilterHandler mentioned earlier. The key code for this logic can be seen below:

class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
   private static final List<ServletComponentHandler> HANDLERS;
   static {
      List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
      servletComponentHandlers.add(new WebServletHandler());
      servletComponentHandlers.add(new WebFilterHandler());
      servletComponentHandlers.add(new WebListenerHandler());
      HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
   }
   // omitted non-key code
   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      if (isRunningInEmbeddedWebServer()) {
         ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
         for (String packageToScan : this.packagesToScan) {
            scanPackage(componentProvider, packageToScan);
         }
      }
   }
   
  private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
     // scan annotations
     for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
        if (candidate instanceof AnnotatedBeanDefinition) {
           // process using WebFilterHandler, etc.
           for (ServletComponentHandler handler : HANDLERS) {
              handler.handle(((AnnotatedBeanDefinition) candidate),
                    (BeanDefinitionRegistry) this.applicationContext);
           }
        }
     }
  }
}

Finally, the WebServletHandler processes all classes annotated with @WebFilter using the template method pattern inherited from the ServletComponentHandler parent class. The key code is as follows:

public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
      BeanDefinitionRegistry registry) {
   BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);
   builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
   builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
   builder.addPropertyValue("filter", beanDefinition);
   // omitted other non-key code
   builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
   registry.registerBeanDefinition(name, builder.getBeanDefinition());
}

From here, we see the FilterRegistrationBean for the first time. By debugging the last line of the above code, we can see that the name of the FilterRegistrationBean we finally register is the same as the name we defined for our WebFilter:

As for the specific creation process of this bean, it is not discussed here. If you are interested, you can continue to explore it further.

Now, let’s move on to the second question: When is TimeCostFilter instantiated?

At this point, we know that the bean we want has been “misinterpreted” as a FilterRegistrationBean, but when is TimeCostFilter actually instantiated? Why doesn’t it become a regular bean?

Regarding this, we can set a breakpoint in the constructor of TimeCostFilter and use debugging to quickly determine its initialization timing. Here I provide a debug screenshot:

In the above key call stack, combined with the source code, you can find some key information:

  1. The FilterRegistrationBean is only created when Tomcat or other containers start.
  2. When FilterRegistrationBean is created (createBean), it creates TimeCostFilter to assemble itself, and TimeCostFilter is created using ResolveInnerBean.
  3. The TimeCostFilter instance is ultimately an InnerBean. We can see some key information about it in the following debug view:

Through the above analysis, you can see that the final TimeCostFilter instance is an InnerBean. So it makes sense that it cannot be automatically injected.

Problem Correction #

Now that we have identified the root cause of the problem, solving it becomes simple.

From the above analysis, we can understand that when using @WebFilter to annotate a filter, the Bean of type TimeCostFilter is not registered in the Spring container. Instead, the registered bean is FilterRegistrationBean. Here, considering that there may be multiple filters, we can modify the example code as follows:

@Controller
@Slf4j
public class StudentController {
    @Autowired
    @Qualifier("com.spring.puzzle.filter.TimeCostFilter")
    FilterRegistrationBean timeCostFilter;
}

The key points here are:

  • The injected type is FilterRegistrationBean, not TimeCostFilter.
  • The injected name is the long name that includes the package, i.e., com.spring.puzzle.filter.TimeCostFilter (not just TimeCostFilter), to ensure accurate matching in the case of multiple filters.

After making the above modifications, the code runs successfully without any errors, meeting our expectations.

Case 2: Accidentally executing doFilter() multiple times in Filter #

In the previous cases, we mainly discussed some common issues encountered when building filters using @ServletComponentScan + @WebFilter.

In actual production, if you need to build a filter that is effective for global paths and has no special requirements (mainly referring to support for some asynchronous features of Servlet 3.0), then you can directly use the Filter interface (or inherit the OncePerRequestFilter class, which is a wrapper class provided by Spring for Filter interface), and use @Component to wrap it as a normal bean in Spring, which can also meet your expectations.

Regardless of which approach you choose, you may encounter a common problem: the business code is executed repeatedly.

Considering that the previous case used @ServletComponentScan + @WebFilter, let’s present another case using the @Component + Filter interface implementation method, so that you can have a better understanding of the use of filters.

First, create a web project using Spring Boot, but you no longer need @ServletComponentScan:

@SpringBootApplication()
public class LearningApplication {
    public static void main(String[] args) {
        SpringApplication.run(LearningApplication.class, args);
        System.out.println("启动成功");
    }
}

Keep the functionality of StudentController unchanged, so you can refer to the previous code directly. In addition, we define a DemoFilter to simulate the problem. This Filter is marked with @Component and implements the Filter interface, which is different from the approach used in the previous case:

@Component
public class DemoFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // Simulate an exception
            System.out.println("Exception occurred during the processing of the Filter");
            throw new RuntimeException();
        } catch (Exception e) {
            chain.doFilter(request, response);
        }
        chain.doFilter(request, response);
    }
}

Once all the code is implemented, the execution results are as follows:

Exception occurred during the processing of the Filter
......User registration succeeded
......User registration succeeded

Here we can see that the business code is executed twice, which is not what we expected.

Our original design goal is to ensure that the execution of the Filter does not affect the execution of the core business. Therefore, when an exception is thrown, we still call chain.doFilter(). However, sometimes we may forget to return in time and mistakenly enter another chain.doFilter(), resulting in the execution of our Filter multiple times.

When checking the code, we often cannot immediately identify the problem. Therefore, this is a typical error, even though the reason is simple. However, with this case, we can analyze why it is executed twice, in order to have a deeper understanding of the execution of Filters.

Case Analysis #

Before analyzing, let me explain the mechanism behind the Filter, which is the Chain of Responsibility pattern.

Taking Tomcat as an example, let’s take a look at the most important class in its Filter implementation, ApplicationFilterChain. It uses the Chain of Responsibility design pattern, which looks very similar to recursive calling.

However, the difference is that recursive calling is when the same object delegates subtasks to the same method to complete, while the Chain of Responsibility is when an object delegates subtasks to other objects with the same method name. The core lies in the passing of the context, FilterChain, and the change of state between different Filter objects. Through this chain of connections, we can handle different business scenarios for the same type of object resource, achieving business decoupling. The structure of the entire FilterChain is like this:

Let’s understand the FilterChain with two questions:

  1. Where is the FilterChain created and where is it initialized and called to activate the Chain of Responsibility for chained calling?
  2. Why can the FilterChain be called in a chain, and what are the internal details of the invocation?

Next, let’s directly check the StandardWrapperValve#invoke(), which is responsible for request processing, to quickly answer the first question:

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Omit non-essential code
    // Create the filterChain
    ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    // Omit non-essential code
    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                 // Omit non-essential code
                 // Execute the filterChain
                 filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                 // Omit non-essential code
             }
    // Omit non-essential code
    }

From the code, we can see that Spring creates the FilterChain via ApplicationFilterFactory.createFilterChain() and then calls its doFilter() method to execute the chain of responsibility. And these steps are initiated by StandardWrapperValve#invoke().

Next, let’s study the second question together, which is why the FilterChain can be called in a chain and what are the internal invocation details.

First, check ApplicationFilterFactory.createFilterChain() to see how the FilterChain is created, as shown below:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
        Wrapper wrapper, Servlet servlet) {
    // Omit non-essential code
    ApplicationFilterChain filterChain = null;
    if (request instanceof Request) {
        // Omit non-essential code
        // Create the Chain
        filterChain = new ApplicationFilterChain();
        // Omit non-essential code
    }
    // Omit non-essential code
    // Add the relevant path-mapped filters to this filter chain
    for (int i = 0; i < filterMaps.length; i++) {
        // Omit non-essential code
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;

Through the code, it can be seen that Spring creates the FilterChain through ApplicationFilterFactory.createFilterChain(), and then uses the doFilter() method to execute the chain of responsibility. And these steps are initiated by StandardWrapperValve#invoke().

Next, let’s study the second question, which is why the FilterChain can be called in a chain and what are the internal invocation details.

First, check ApplicationFilterFactory.createFilterChain() to see how the FilterChain is created and initialized, as shown below:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
        Wrapper wrapper, Servlet servlet) {
    // Omit non-essential code
    ApplicationFilterChain filterChain = null;
    if (request instanceof Request) {
        // Omit non-essential code
        // Create the Chain
        filterChain = new ApplicationFilterChain();
        // Omit non-essential code
    }
    // Omit non-essential code
    // Add the relevant path-mapped filters to this filter chain
    for (int i = 0; i < filterMaps.length; i++) {
        // Omit non-essential code
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;

With the code, it can be seen that when the Request object is passed in as a parameter, a new ApplicationFilterChain object is created.

}
// Add filterConfig to Chain
filterChain.addFilter(filterConfig);
}

// Omit non-critical code
return filterChain;
}

It creates a FilterChain and adds each filter to the FilterChain one by one. Next, let’s take a look at the ApplicationFilterChain class and its addFilter() method:

// Omit non-critical code
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private int pos = 0;
private int n = 0;
// Omit non-critical code
void addFilter(ApplicationFilterConfig filterConfig) {
    for (ApplicationFilterConfig filter : filters)
        if (filter == filterConfig)
            return;

    if (n == filters.length) {
        ApplicationFilterConfig[] newFilters =
            new ApplicationFilterConfig[n + INCREMENT];
        System.arraycopy(filters, 0, newFilters, 0, n);
        filters = newFilters;
    }
    filters[n++] = filterConfig;
}

In the ApplicationFilterChain class, there are three variables declared: an array of type ApplicationFilterConfig named filters, a counter named n which keeps track of the total number of filters, and a variable named pos which indicates the number of filters that have been executed during the process.

Each initialized filter is added to the filters array using filterChain.addFilter(), which is of type ApplicationFilterConfig, and the total number of filters is updated to the length of the filters array. With this, Spring has completed the preparation work for creating the FilterChain.

Next, let’s take a look at the details of executing the FilterChain, specifically the doFilter() method of ApplicationFilterChain:

public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {
    if (Globals.IS_SECURITY_ENABLED) {
        // Omit non-critical code
        internalDoFilter(request,response);
        // Omit non-critical code
    } else {
        internalDoFilter(request,response);
    }
}

The logic here is delegated to the private method internalDoFilter of the current class, which is implemented as follows:

private void internalDoFilter(ServletRequest request,
                              ServletResponse response) {
    if (pos < n) {
        // pos will increment
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            Filter filter = filterConfig.getFilter();
            // Omit non-critical code
            // Execute filter
            filter.doFilter(request, response, this);
            // Omit non-critical code
        }
        // Omit non-critical code
        return;
    }
    // Execute actual business logic
    servlet.service(request, response);
}

We can summarize the key points:

  • internalDoFilter() in ApplicationFilterChain is the core logic of the filter.
  • The member variable filters in ApplicationFilterChain maintains all user-defined filters.
  • n is the total number of filters, and pos is the number of filters that have been executed so far.
  • Each time internalDoFilter() is called, the pos variable is incremented by 1, indicating the next filter to be taken from the Filters member variable array.
  • filter.doFilter(request, response, this) will call the doFilter() method implemented by the filter, with this as the third parameter, which is the current ApplicationFilterChain instance. This means that the user needs to explicitly call javax.servlet.FilterChain#doFilter once in the filter to complete the entire chain.
  • pos < n means that all filters must be executed before the servlet.service(request, response) can be called to execute the actual business logic.

After all the filters are executed, the code calls the servlet.service(request, response) method. From the screenshot of the call stack below, we can see that after going through a long seemingly circular call stack, we finally arrived at the saveUser() method in the controller layer. We won’t go into detail about this process.

After analyzing all of this, let’s consider the problem case again.

In the doFilter() method of the DemoFilter code, the code executed once in the exception-catching part and then executed again outside the try block. Therefore, when an exception is thrown, doFilter() will clearly be executed twice, and correspondingly, the servlet.service(request, response) method and the corresponding controller handling method will also be executed twice.

You might want to review the filter execution flowchart mentioned earlier, and I believe you will gain more insights.

Problem Fix #

Now, all we need to do is to remove the duplicate filterChain.doFilter(request, response), so the code becomes like this:

@Component
public class DemoFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // Simulate an exception
            System.out.println("An exception occurred while processing the filter");
            throw new RuntimeException();
        } catch (Exception e) {
            // Remove the following line of code
            // chain.doFilter(request, response);
        }
        chain.doFilter(request, response);
    }
}

Rerun the program and test it again, and the result will meet the expected outcome. The business logic is executed only once. Looking back at this problem, I think you should be aware that when using filters, you must be careful not to call FilterChain#doFilter() multiple times, no matter how you call it.

Key Review #

Through the study of this lesson, I believe you now have a deeper understanding of filters. Let’s summarize the key points:

  1. Filters created using @WebFilter cannot be automatically injected based on their filter definition types, because this type of filter is presented as an internal bean and is ultimately presented to Spring through FilterRegistrationBean. Therefore, we can use FilterRegistrationBean to autowire and configure the filter. An example is as follows:
@Autowired
@Qualifier("com.spring.puzzle.filter.TimeCostFilter")
FilterRegistrationBean timeCostFilter;
  1. In the execution of filters, we must be careful not to call doFilter() multiple times, otherwise it may result in multiple executions of business code. This problem often occurs due to “carelessness”, but in order to understand the phenomenon caused by this problem, we must have an understanding of the filter process. You can refer to the core execution flow of filters:

Filter Execution Flow

Based on this flow, we can further refine the following key steps:

  • When a request arrives, it triggers the execution of StandardWrapperValve’s invoke() method, which creates an ApplicationFilterChain and triggers the execution of filters through ApplicationFilterChain#doFilter().
  • The doFilter() method of ApplicationFilterChain executes its private method internalDoFilter.
  • In the internalDoFilter method, the next filter is obtained, and doFilter() is called with request, response, and this (the current ApplicationFilterChain instance) as parameters:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
  • In the doFilter() method of the filter class, the defined actions of the filter are executed and the processing is continued. The third parameter, ApplicationFilterChain, is obtained and its doFilter() method is called.
  • At this point, steps 2, 3, and 4 are repeated in a loop until all filter classes in step 3 are executed.
  • After all filters have been executed, the servlet.service(request, response) method is executed, ultimately invoking the corresponding controller method.

These are the key steps of filter execution. I hope you can remember them.

Thought Question #

In case 2, we mentioned that it is important to avoid calling FilterChain#doFilter() multiple times in a filter. So, what would happen if a filter fails to call this method even once under certain circumstances?

For reference, consider the transformed DemoFilter:

@Component
public class DemoFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("do some logic");
    }
}

We look forward to your thoughts. Please leave your comments in the comment section below!