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