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