14 Spring Web Filters Usage Common Errors Part 2

14 Spring Web Filters Usage Common Errors Part 2 #

Hello, I am Fu Jian.

In the previous lesson, we learned about the working principles of filters at runtime. In this lesson, we will continue to learn about container startup, filter initialization, and sorting registration through two error cases. By understanding these concepts, you will have more confidence in using filters effectively. Now, let’s take a closer look at them.

Case 1: @WebFilter annotation with @Order is not effective #

Let’s assume that we are still developing the student information management system based on Spring Boot, which was covered in the previous lesson. Let’s have a quick review of the code used in the previous lesson.

Firstly, here’s the code to create the startup program:

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

The code for the implemented controller is as follows:

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

The above code provides a Restful interface “/regStudent”. This interface has only one parameter named “name”, and it returns “success” upon successful registration.

Now, let’s implement two new filters. The code is as follows:

AuthFilter: For example, limit registration of new users to specific IP address ranges (such as within a campus network). In this case, we will simulate it by simply sleeping for 1 second.

@WebFilter
@Slf4j
@Order(2)
public class AuthFilter implements Filter {
    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        if(isPassAuth()){
            System.out.println("Passed authorization");
            chain.doFilter(request, response);
        }else{
            System.out.println("Failed to pass authorization");
            ((HttpServletResponse)response).sendError(401);
        }
    }
    private boolean isPassAuth() throws InterruptedException {
        System.out.println("Checking authorization");
        Thread.sleep(1000);
        return true;
    }
}

TimeCostFilter: Calculate the execution time for registering a student, including the authorization process.

@WebFilter
@Slf4j
@Order(1)
public class TimeCostFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("#Start calculating interface execution time");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("#Execution time (ms): " + time);
    }
}

In the above code, we have used the @Order annotation with the expectation that the TimeCostFilter should be executed first. Since the TimeCostFilter is designed to measure the performance of the interface, it needs to include the authorization process carried out by the AuthFilter.

After implementing all the code, the execution result is as follows:

Checking authorization
Passed authorization
#Start calculating interface execution time
......User registration successful
#Execution time (ms): 33

From the result, we can see that the execution time does not include the authorization process, which does not meet our expectations, considering that we have used @Order. However, if we swap the values specified by Order, you will not see any effect. Why is this happening? Is it not possible to use Order for sorting WebFilters? Let’s analyze this problem and the underlying principles in detail.

Case Analysis #

Based on what we have learned from the previous lesson, we know that when a request arrives, it will invoke the invoke() method of StandardWrapperValve. This method creates an ApplicationFilterChain and triggers the execution of filters through ApplicationFilterChain#doFilter(), ultimately calling the internal private method internalDoFilter(). Let’s try to find some clues in internalDoFilter():

private void internalDoFilter(ServletRequest request,
                                ServletResponse response)
    throws IOException, ServletException {

    // Call the next filter if there is one
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            Filter filter = filterConfig.getFilter();

From the above code, we can see that the order of filter execution is determined by the Filters class member variable, and the Filters variable is obtained by sequentially traversing the FilterMaps member variables of StandardContext during container startup:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
        Wrapper wrapper, Servlet servlet) {

    // Omitted non-relevant code
    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();
    // Omitted non-relevant code
    // Add the relevant path-mapped filters to this filter chain
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    // Omitted non-relevant code
    // Return the completed filter chain
    return filterChain;
}

Let’s continue to find references to the FilterMaps member variable in StandardContext that writes to it. We found the addFilterMapBefore():

public void addFilterMapBefore(FilterMap filterMap) {
    validateFilterMap(filterMap);
    // Add this filter mapping to our registered set
    filterMaps.addBefore(filterMap);
    fireContainerEvent("addFilterMap", filterMap);
}

To this point, we already know that the order of filter execution is determined by the order of elements in the FilterMaps member variable of the StandardContext class. Furthermore, FilterMaps is a wrapped array, so we just need to further understand the arrangement order of the elements in FilterMaps.

Let’s put a breakpoint in addFilterMapBefore() and try to find some clues from the call stack:

addFilterMapBefore:2992, StandardContext
addMappingForUrlPatterns:107, ApplicationFilterRegistration
configure:229, AbstractFilterRegistrationBean
configure:44, AbstractFilterRegistrationBean
register:113, DynamicRegistrationBean
onStartup:53, RegistrationBean
selfInitialize:228, ServletWebServerApplicationContext
// Omitted non-relevant code

It can be seen that Spring calls selfInitialize() starting from selfInitialize() and continues to call addFilterMapBefore() step by step according to the order shown in the call stack, ultimately reaching addFilterMapBefore().

So, where does this selfInitialize() come from? Think about it for a moment, and I’ll explain it further in the follow-up question. Now let’s continue examining the details of selfInitialize().

First, let’s look at getServletContextInitializerBeans() in the above code. This method returns a collection of ServletContextInitializer type beans, and the order of this collection determines the order of the addFilterMapBefore() calls. This, in turn, determines the order of elements in FilterMaps, ultimately determining the execution order of filters.

The implementation of getServletContextInitializerBeans() is very simple. It simply returns an instance of the ServletContextInitializerBeans class. The code is as follows:

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
   return new ServletContextInitializerBeans(getBeanFactory());
}

The return value of this method is a Collection, indicating that ServletContextInitializerBeans is a collection class that inherits from the AbstractCollection abstract class. This is why selfInitialize() can iterate over the instance object of ServletContextInitializerBeans.

Since ServletContextInitializerBeans is a collection class, we can first look at its iterator() method to see what it iterates over.

@Override
public Iterator<ServletContextInitializer> iterator() {
   return this.sortedList.iterator();
}

The collection class exposes sortedList as the collection to iterate over, indicating that what selfInitialize() ultimately iterates over is the sortedList member variable.

From this, we can further deduce the following conclusion: in selfInitialize(), the ServletContextInitializer type beans obtained through getServletContextInitializerBeans() are the sortedList member variable of type ServletContextInitializerBeans. Conversely, the order of filter bean elements in sortedList determines the execution order of the filters.

Now let’s continue examining the constructor of ServletContextInitializerBeans as follows:

public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
      Class<? extends ServletContextInitializer>... initializerTypes) {
   this.initializers = new LinkedMultiValueMap<>();
   this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
         : Collections.singletonList(ServletContextInitializer.class);
   addServletContextInitializerBeans(beanFactory);
   addAdaptableBeans(beanFactory);
   List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
         .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
         .collect(Collectors.toList());
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
   logMappings(this.initializers);
}

From line 8, we can see that the member variable we are interested in, this.sortedList, is sorted based on the AnnotationAwareOrderComparator, using the values of the member variable this.initializers.

Let’s continue looking at the AnnotationAwareOrderComparator comparator. Ignoring the details of the comparator invocation process, it ultimately obtains the order value required by the comparator in two ways to determine the arrangement order of sortedInitializers:

  • If the object element to be sorted itself implements the Order interface, it directly obtains the order value through getOrder().
  • Otherwise, it executes OrderUtils.findOrder() to obtain the @Order attribute of the object class.

It is worth noting that since the values of this.initializers are of type ServletContextInitializer, which implements the Ordered interface, the comparator clearly uses getOrder() to obtain the order value required by the comparator, and the corresponding member variable is order.

Let’s continue examining where the elements of this.initializers are added. We ultimately know that both the addServletContextInitializerBeans() and addAdaptableBeans() methods build instances of subclasses of ServletContextInitializer and add them to the this.initializers member variable. Here, we will only study addServletContextInitializerBeans since the way we add filters (using @WebFilter annotation) will only take effect through this method.

In this method, Spring instantiates all subclasses of ServletContextInitializer by calling getOrderedBeansOfType():

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
   for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
      for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
            initializerType)) {
         addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
      }
   }
}

Based on their different types, the addServletContextInitializerBean() method is called. We can see that subclasses of ServletContextInitializer include ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean, which correspond to the three major elements of a Servlet.

Here we only need to focus on FilterRegistrationBean corresponding to filters. Clearly, FilterRegistrationBean is a subclass of ServletContextInitializer (implementing the Ordered interface), and the execution priority is determined by the value of the order member variable.

private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
      ListableBeanFactory beanFactory) {
   if (initializer instanceof ServletRegistrationBean) {
      Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
      addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
   }
   else if (initializer instanceof FilterRegistrationBean) {
      Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
      addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
   }
   else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
      String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
      addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
   }
}

In this method, if the initializer is an instance of ServletRegistrationBean, it means it is a Servlet, and it calls addServletContextInitializerBean() accordingly. Similarly, if the initializer is an instance of FilterRegistrationBean, it means it is a Filter, and it also calls addServletContextInitializerBean() accordingly.

else if (initializer instanceof ServletListenerRegistrationBean) {
          EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
          addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
       }
       else {
          addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
                initializer);
       }
    }
    

Lastly, add to the `this.initializers` member variable:
```java
private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer,
          ListableBeanFactory beanFactory, Object source) {
       this.initializers.add(type, initializer);
    // Omit non-critical code
    }

In the above code, we see FilterRegistrationBean again. But the question is, we haven’t defined FilterRegistrationBean, so where is FilterRegistrationBean defined? Does the order instance variable have any specific value logic?

Let’s recall the example from the previous lesson. In this example, FilterRegistrationBean is dynamically constructed in the doHandle() method of the WebFilterHandler class:

class WebFilterHandler extends ServletComponentHandler {

   WebFilterHandler() {
      super(WebFilter.class);
   }

   @Override
   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);
      builder.addPropertyValue("initParameters", extractInitParameters(attributes));
      String name = determineName(attributes, beanDefinition);
      builder.addPropertyValue("name", name);
      builder.addPropertyValue("servletNames", attributes.get("servletNames"));
      builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
      registry.registerBeanDefinition(name, builder.getBeanDefinition());
   }
   // Omit non-critical code

Here, I’ve pasted the logic from the doHandle() method of WebFilterHandler again (i.e., dynamically constructing a FilterRegistrationBean BeanDefinition using BeanDefinitionBuilder). However, unfortunately, the order value is not set here, let alone being set based on the specified value of @Order.

At this point, we finally see the essence of the problem. All classes annotated with @WebFilter are ultimately wrapped into a FilterRegistrationBean class BeanDefinition here. Although FilterRegistrationBean also implements the Ordered interface, no value is populated here because all the attributes are obtained from @WebFilter, and @WebFilter itself does not specify any attributes that can assist with sorting.

Now let’s summarize: the execution order of filters is determined by the following chain of dependencies:

The value of the order attribute in RegistrationBean -> The order of elements in the sortedList member variable of ServletContextInitializerBeans -> The order in which ServletContextInitializerBeans are traversed in the selfInitialize() method of ServletWebServerApplicationContext -> The order of invocations in addFilterMapBefore() -> The order of elements in filterMaps -> The execution order of filters.

As we can see, the value of the order attribute in RegistrationBean ultimately determines the execution order of filters. However, unfortunately, when using @WebFilter, the constructed FilterRegistrationBean does not set the order attribute based on the value of @Order, so @Order is ineffective.

Fixing the problem #

Now that we understand the initialization process of some necessary classes before Spring starts the Web service and we have figured out why using both @Order and @WebFilter fails, solving this problem is not so simple.

First, I’ll provide you with a common practice, which is to implement your own FilterRegistrationBean to configure and add filters, instead of using @WebFilter. The code is as follows:

@Configuration
public class FilterConfiguration {
    @Bean
    public FilterRegistrationBean authFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AuthFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(2);
        return registration;
    }

    @Bean
    public FilterRegistrationBean timeCostFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new TimeCostFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }
}

Given the logic we examined in the source code, while WebFilterHandler method creates a FilterRegistrationBean BeanDefinition in doHandle(), the value of order is not set.

So here, we directly instantiate instances of FilterRegistrationBean and set their order using setOrder(). Also, don’t forget to remove @WebFilter from the AuthFilter and TimeCostFilter classes. This way, the problem is solved.

Case 2: Filter Executed Multiple Times #

We continue to use the code from the previous case to solve the sorting problem. Some may wonder if there are other solutions. For example, can we add @Component to both filters to make @Order work? The code is as follows.

AuthFilter:

@WebFilter
@Slf4j
@Order(2)
@Component
public class AuthFilter implements Filter {
    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        if(isPassAuth()){
            System.out.println("Authorized");
            chain.doFilter(request, response);
        }else{
            System.out.println("Not Authorized");
            ((HttpServletResponse)response).sendError(401);
        }
    }
    private boolean isPassAuth() throws InterruptedException {
        System.out.println("Checking authorization");
        Thread.sleep(1000);
        return true;
    }
}

TimeCostFilter:

@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("#Calculating interface execution time");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("#Execution time (ms): " + time);
    }
}

The final execution result is as follows:

#Calculating interface execution time
Checking authorization
Authorized
Checking authorization
Authorized
#Calculating interface execution time
......User registration successful
#Execution time (ms): 73
#Execution time (ms): 2075

Changing the Order value of AuthFilter to 0 and continuing the test, we get the following result:

Checking authorization
Authorized
#Calculating interface execution time
Checking authorization
Authorized
#Calculating interface execution time
......User registration successful
#Execution time (ms): 96
#Execution time (ms): 1100

Obviously, based on the value of Order, we can freely adjust the execution order of the filters. But we will be surprised to find that the filter itself is executed twice, which clearly does not meet our expectations! So how do we understand this phenomenon?

Case Analysis #

From Case 1, we learned that filters annotated with @WebFilter are dynamically wrapped into FilterRegistrationBean classes by the WebServletHandler class, rather than being Filter types.

When we add @Component to our custom filters, we can make a bold guess: in theory, Spring will wrap a new filter based on the current class, so doFilter() is executed twice. Therefore, the seemingly strange test results are also reasonable.

Let’s continue to find the truth from the source code and continue to look up the constructor of ServletContextInitializerBeans as follows:

public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
      Class<? extends ServletContextInitializer>... initializerTypes) {
   this.initializers = new LinkedMultiValueMap<>();
   this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
         : Collections.singletonList(ServletContextInitializer.class);
   addServletContextInitializerBeans(beanFactory);
   addAdaptableBeans(beanFactory);
   List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
         .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
         .collect(Collectors.toList());
   this.sortedList = Collections.unmodifiableList(sortedInitializers);
   logMappings(this.initializers);
}

In the previous case, we focused on addServletContextInitializerBeans(), learned that its function is to instantiate and register all filters of type FilterRegistrationBean (strictly speaking, instantiate and register all ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean, but here we only focus on FilterRegistrationBean).

The line 7 addAdaptableBeans() has the effect of instantiating all classes that implement the Filter interface (strictly speaking, instantiating and registering all classes that implement the Servlet, Filter, and EventListener interfaces), and then wrapping them one by one into FilterRegistrationBean.

The reason why Spring can directly instantiate the FilterRegistrationBean type filters. This is because:

  • The WebFilterHandler-related classes scan @WebFilter and dynamically construct the BeanDefinition of type FilterRegistrationBean, which is then registered with Spring;
  • Or we can use @Bean to explicitly instantiate FilterRegistrationBean and register it with Spring, as shown in the solution in Case 1.

But how can Spring directly instantiate filters of type Filter? I believe you already have the answer: Any class annotated with @Component can be automatically registered with Spring and can be directly instantiated by Spring.

Now we can directly see addAdaptableBeans(), which calls addAsRegistrationBean(), with beanType as Filter.class:

protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
   // Omitted non-core code
   addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
   // Omitted non-core code
}

Let’s continue to see the addAsRegistrationBean() method that is ultimately called:

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
      Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
   List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
   for (Entry<String, B> entry : entries) {
      String beanName = entry.getKey();
      B bean = entry.getValue();
      if (this.seen.add(bean)) {
         // One that we haven't already seen
         RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
         int order = getOrder(bean);
         registration.setOrder(order);
         this.initializers.add(type, registration);
         if (logger.isTraceEnabled()) {
            logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                  + order + ", resource=" + getResourceDescription(beanName, beanFactory));
         }
      }
   }
}

The main logic is as follows:

  • getOrderedBeansOfType() creates instances of all classes that are subclasses of Filter, which means all classes that implement the Filter interface and are annotated with @Component;
  • Iterate through these instances of Filter classes and wrap them one by one as RegistrationBean using RegistrationBeanAdapter;
  • Obtain the Order value of the Filter class instance and set it to the wrapped class RegistrationBean;
  • Add the RegistrationBean to this.initializers.

So far, we know that when a filter is annotated with both @WebFilter and @Component, two instances of FilterRegistrationBean are generated. Both addServletContextInitializerBeans() and addAdaptableBeans() will create instances of FilterRegistrationBean, but the difference is:

  • @WebFilter will instantiate and register all dynamically generated FilterRegistrationBean type filters through addServletContextInitializerBeans();
  • @Component will instantiate all classes that implement the Filter interface and then wrap them one by one as FilterRegistrationBean type filters through addAdaptableBeans().

Issue Resolution #

To address the ordering issue mentioned, we can continue to refer to the problem resolution section in Case 1. In addition, we can remove @WebFilter and keep @Component for modification. The modified filter example is as follows:

//@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
   //Omitted non-core code
}

Key Review #

In this lesson, we analyzed the entire process of how filters are registered, wrapped, and instantiated in the Spring framework. Let’s review the key points again.

The similarities between @WebFilter and @Component are:

  • They are both ultimately wrapped and instantiated as FilterRegistrationBean.
  • They are both instantiated in the constructor of ServletContextInitializerBeans.

The differences between @WebFilter and @Component are:

  • The filter marked with @WebFilter is wrapped into a BeanDefinition of type FilterRegistrationBean through the BeanFactoryPostProcessors extension point, and then instantiated in ServletContextInitializerBeans.addServletContextInitializerBeans(); while the filter class marked with @Component is instantiated as a Filter type in ServletContextInitializerBeans.addAdaptableBeans() and then wrapped as a RegistrationBean type.
  • The filter marked with @WebFilter does not have the Order property injected, but the filter marked with @Component has the Order property injected in ServletContextInitializerBeans.addAdaptableBeans().

Thought Question #

In the two cases discussed in this lesson, both occurred during the startup of the Tomcat container. However, do you understand how Spring integrates with Tomcat to register these filters during startup?

Looking forward to your thoughts! Let’s discuss in the comments section!