Spring source code parsing -- spring web Filter parsing

brief introduction

In the last few articles, we explored the code related to request processing. This article begins to explore some operation codes before request processing, such as Filter. This article explores Filter initialization, request processing and other related codes.

preface

First, simply define the relevant test code:

Startup class:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class SpringExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringExampleApplication.class, args);
    }
}

Controller related codes:

import com.example.springexample.vo.User;
import org.springframework.web.bind.annotation.*;

@RestController
public class HelloWorld {

    @GetMapping("/")
    public String helloWorld(@RequestParam(value = "id") Integer id,
                             @RequestParam(value = "name") String name) {
        return "Hello world:" + id;
    }

    @GetMapping("/test1")
    public String helloWorld1(@RequestParam(value = "id") Integer id) {
        return "Hello world:" + id;
    }

    @PostMapping("/test2")
    public String helloWorld2(@RequestBody User user) {
        return "Hello world:" + user.toString();
    }
}

Filter related codes:

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

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

@Slf4j
@Order(1)
@WebFilter(filterName = "MyFilter1", urlPatterns = "/test1")
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 1");
        chain.doFilter(request, response);
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

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

@Slf4j
@Order(2)
@WebFilter(filterName = "MyFilter2", urlPatterns = "/test2")
public class MyFilter2 implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("My filter log 2");
        chain.doFilter(request, response);
    }
}

The core code is as above, and the related request Filter processing is as follows:

  • /: neither Filter is triggered
  • /test1: trigger MyFilter1
  • /test2: trigger MyFilter2

In line with our expectations, let's explore the source code:

  • 1. How is filter initialized
  • 2. How does filter correspond to related URL requests

Source code analysis

When exploring, you directly break the MyFilter class and explore the stack step by step. The relevant source code is as follows

Filter initialization

The first is to find the relevant code obtained and traversed by the Filter related class

In the following functions, the built-in and self-defined filters of the system are obtained through traversal (the details of how to obtain them will not be discussed, but all filters can be obtained here)

    # ServletWebServerApplicationContext.class
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        this.prepareWebApplicationContext(servletContext);
        this.registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
	// Get all the filters and traverse the processing
        Iterator var2 = this.getServletContextInitializerBeans().iterator();
        while(var2.hasNext()) {
            ServletContextInitializer beans = (ServletContextInitializer)var2.next();
            beans.onStartup(servletContext);
        }

    }

Next, go to the code section related to adding and registering Filter

    # AbstractFilterRegistrationBean.class
    protected void configure(Dynamic registration) {
        super.configure(registration);
        EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
        if (dispatcherTypes == null) {
            T filter = this.getFilter();
            if (ClassUtils.isPresent("org.springframework.web.filter.OncePerRequestFilter", filter.getClass().getClassLoader()) && filter instanceof OncePerRequestFilter) {
                dispatcherTypes = EnumSet.allOf(DispatcherType.class);
            } else {
                dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
            }
        }

        Set<String> servletNames = new LinkedHashSet();
        Iterator var4 = this.servletRegistrationBeans.iterator();
	// The role of this part of the code is not clear, which will be explored later
        while(var4.hasNext()) {
            ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next();
            servletNames.add(servletRegistrationBean.getServletName());
        }

        servletNames.addAll(this.servletNames);
        if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
	    // By default, the system takes this part of processing, and the interception path is:/**
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
        } else {
            if (!servletNames.isEmpty()) {
                registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames));
            }
	    // All our customized paths are here, and the interception paths are configured: / test1 and / test2
            if (!this.urlPatterns.isEmpty()) {
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns));
            }
        }

    }

In the above code, we see that there are two ways to configure the interception path:

  • servletNames
  • urlPatterns

The next step is to add the Filter

    # ApplicationFilterRegistration.java
    public void addMappingForUrlPatterns(
            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
            String... urlPatterns) {

        FilterMap filterMap = new FilterMap();

        filterMap.setFilterName(filterDef.getFilterName());

        if (dispatcherTypes != null) {
            for (DispatcherType dispatcherType : dispatcherTypes) {
                filterMap.setDispatcher(dispatcherType.name());
            }
        }

	// Related codes added by Filter
        if (urlPatterns != null) {
            // % decoded (if necessary) using UTF-8
            for (String urlPattern : urlPatterns) {
                filterMap.addURLPattern(urlPattern);
            }

            if (isMatchAfter) {
                context.addFilterMap(filterMap);
            } else {
                context.addFilterMapBefore(filterMap);
            }
        }
        // else error?
    }

In the above code, Filter adds two codes: one is added to the Map and the other is added to the context. The latter still seems to have other channels. Continue to study later

Here, the initialization of the Filter is completed. Let's take a look at the code of the use aspect

Filter match add

In daily development, we will configure relevant matching paths for Filter. Not all requests are filtered. What is the matching of this piece? Next, initiate a request to explore the matching addition of Filter

The following code is the core Filter matching processing, but the previous trigger calls have not been sorted out yet. Wrapper seems to be very critical. Ignore it for the time being and look at the related Filter matching processing first

    # ApplicationFilterFactory.java
    public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
	......
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
	// Get the Filter and get the Filter initialized above
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0)) {
            return filterChain;
        }

        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

	// Requested path
        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

	// Here's the match
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

        // Return the completed filter chain
        return filterChain;
    }

    // After the above trigger call, you will come to the related diam of adding Filter to the list below
    # ApplicationFilterChain.java
    void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        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;

    }

The above is the core code added by the core Filter matching. The noteworthy points are as follows:

  • Will be matched and added twice
  • There are three ways to match:
    • matchDispatcher(filterMap, dispatcher)
    • matchFiltersURL(filterMap, requestPath)
    • matchFiltersServlet(filterMap, servletName)

Here are the following two questions:

  • Why do you need to separate the two matches: to distinguish between the front and back filters?
  • The Filter loop matching twice seems to be the difference between the matching path requestPath and the matching ServletName. What are the differences between the two? Why do they need to be separated?

At present, I haven't found any definite clues about the above questions. I should be able to fill them in later exploration

Filter trigger

After the above Filter matching, the requested Filter will be initialized, and then enter the processing call phase

    # ApplicationFilterChain.java
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                        (java.security.PrivilegedExceptionAction<Void>) () -> {
                            internalDoFilter(req,res);
                            return null;
                        }
                );
            } catch( PrivilegedActionException pe) {
		    ......
            }
        } else {
	    // Call trigger
            internalDoFilter(request,response);
        }
    }

    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 {
		// Get current Filter
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
		    // Call trigger
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
		    ......
            }
            return;
        }

        // The following code is suspected to end the Filter and trigger the request function to process the relevant code. Mark first and explore later
        // We fell off the end of the chain -- call the servlet instance
	......
    }

The above is the core code triggered by the Filter call. There are types of related codes in spring cloudgateway and Netty. Looking at this code mode, it is very classic, but the details will be studied later

summary

Through the above code analysis, we have found the basic core code of Filter:

  • 1. Initialization of Filter: initialize the Filter when the application starts
  • 2.Filter matching and addition: for unused request paths, different filter links will be generated by matching
    • Is there a cache available: after experiments, each request will be matched
  • 3. Chain call processing

There are still many questions. It seems that the related to Order sorting has not been found. If you are interested, you can find it yourself

Reference link

Keywords: Java Spring source code analysis filter

Added by jsimmons on Sun, 02 Jan 2022 12:31:55 +0200