Introduction to the

After exploring the code associated with request processing in the previous articles, this article begins by exploring some of the pre-request processing code, such as Filter. This article explores code related to Filter initialization, request handling, and more.

preface

Let’s start by simply defining the relevant test code:

Start the 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); }}Copy the code

Controller code:

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(); }}Copy the code

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); }}Copy the code
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); }}Copy the code

The core code is shown above, and the relevant request Filter is processed as follows:

  • / : Neither Filter is triggered
  • /test1: triggers MyFilter1
  • /test2: triggers MyFilter2

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

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

The source code parsing

Exploration is directly on the MyFilter class breakpoint, step by step to explore the stack, the relevant source code is as follows

The Filter initialization

The first step is to find the relevant code that the Filter related class retrieves and iterates over

In the following function, the traversal retrieves the system’s built-in and our own defined filters (we won’t go into the details of how to get them, but we can get all of them here)

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

Next comes the code related to adding a registration 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();
	// This part of the code is not clear, for later exploration
        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 interception path is: /**
            registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
        } else {
            if(! servletNames.isEmpty()) { registration.addMappingForServletNames(dispatcherTypes,this.matchAfter, StringUtils.toStringArray(servletNames));
            }
	    // The interception path is the one we configured: /test1,/test2
            if (!this.urlPatterns.isEmpty()) {
                registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns)); }}}Copy the code

In the code above, we see that intercepting paths can be configured in two ways:

  • servletNames
  • urlPatterns

And then you go down and you 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()); }}// Filter adds the relevant code
        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?
    }
Copy the code

In the above code, two codes are added to Filter: one is added to Map, and the other is added to context. The latter seems to have other rules, so we will continue to study later

Now that the Filter is initialized, let’s look at the code that uses the aspect

Filter Matching add

In daily development, Filter will be configured with relevant matching paths. Not all requests will be filtered. What is the matching of this piece? Next, make a request to explore the Filter’s matching addition

The following code is the core Filter matching processing, but the previous trigger call is not clear for the moment, the Wrapper seems to be the key, ignore it for the moment, and look at the Filter matching processing related

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

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

        String servletName = wrapper.getName();

	// The match is made here
        // 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 comes the related DIAM below to add Filter to the list
    # 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;

    }
Copy the code

Above is the core Filter matching added core code, notable points have the following:

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

Here are two questions:

  • Why is it necessary to separate the two matches: to distinguish the Filter from the Filter?
  • That sounds like the two matching Filter cycle, matching path requestPath and ServletName difference, the difference between, why we need to separate?

As for the above question, I have not found a definite clue so far. In the following exploration, I should be able to fill it

The Filter is triggered

After matching the Filter above, the requested Filter is initialized, and it is time to proceed to the processing call

    # 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 triggerinternalDoFilter(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 the 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 has a suspected finalizer, which triggers the request function to process the relevant code. Mark it and explore it later
        // We fell off the end of the chain -- call the servlet instance. }Copy the code

The above is the core code of the Filter call trigger, the chain trigger call, in SpringCloudGateway and Netty have related code, look at this code pattern is very classic ah, but the details will be studied later

conclusion

Through the above code analysis, the basic core code of Filter has been found:

  • 1. Initialization of the Filter: Initializes the Filter when the application starts
  • 2.Filter matching: For unused request paths, different Filter links are generated
    • Is there any cache in it: After experiments, every request is matched
  • 3. Chain call processing

There are still a lot of questions, and the Order of the relevant seems not found, interested can find the next

Refer to the link

  • Spring source code parsing series