在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如Filter。本篇探索Filter初始化、请求处理等相关代码。
说先简单的定义相关的测试代码:
启动类:
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相关代码:
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相关代码:
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); } }
核心的代码如上,相关的请求Filter处理如下:
符合我们的使用预期,接下来我们到源码中探索:
探索时是直接在MyFilter类打上断点,一步步探索堆栈,得到的相关源码如下
首先是找到Filter相关类获取并遍历的相关代码
下面的函数中,遍历获得了系统内置的和我们自己定义的Filter(如何获取的细节先不深究,在这里能得到所有的Filter)
# ServletWebServerApplicationContext.class private void selfInitialize(ServletContext servletContext) throws ServletException { this.prepareWebApplicationContext(servletContext); this.registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext); // 得到了所有的Filter,遍历处理 Iterator var2 = this.getServletContextInitializerBeans().iterator(); while(var2.hasNext()) { ServletContextInitializer beans = (ServletContextInitializer)var2.next(); beans.onStartup(servletContext); } }
接下来来到添加注册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(); // 这部分代码作用尚不明确,留待以后探索 while(var4.hasNext()) { ServletRegistrationBean<?> servletRegistrationBean = (ServletRegistrationBean)var4.next(); servletNames.add(servletRegistrationBean.getServletName()); } servletNames.addAll(this.servletNames); if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) { // 系统默认的都走的这部分处理,拦截路径默认都是:/** registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS); } else { if (!servletNames.isEmpty()) { registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames)); } // 我们自定义的都都了这里,拦截路径就是我们配置的:/test1,/test2 if (!this.urlPatterns.isEmpty()) { registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns)); } } }
在上面的代码中,我们看到了拦截路径的配置有两个方式:
接着跟下去,下降就是将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添加的相关代码 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? }
上面的代码中,Filter添加有两处代码:一个是添加到Map中,一个是context中,后者好像还是有其他道道,后面继续研究看看
到这里,Filter就初始化完成了,下面看看使用方面的代码
在日常开发中,Filter我们都会配置相关的匹配路径,不是所有的请求都进行过滤,那这块的匹配是怎么的?接下来就发起请求,探索Filter的匹配添加
下面的代码是核心的Filter匹配处理,但前面的触发调用目前暂时还没有梳理清楚,Wrapper好像挺关键的,暂时忽略它,先看Filter匹配处理相关的
# ApplicationFilterFactory.java public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { ...... // Acquire the filter mappings for this Context StandardContext context = (StandardContext) wrapper.getParent(); // 获取Filter,得到上面初始化的Filter 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); // 请求的路径 String requestPath = null; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null){ requestPath = attribute.toString(); } String servletName = wrapper.getName(); // 在这里就进行匹配了 // 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; } // 上面的触发调用后,就来到了下面的将Filter添加到列表中的相关diam # 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; }
上面的就是核心的Filter匹配添加的核心代码,值得注意的点有下面几个:
这里就有下面两点疑问了:
关于上面的疑问目前我也没找到确定的线索,后面的探索中,应该能把它补上
经过上面的Filter匹配,请求的Filter就初始化好了,下面就进入到处理调用环节
# 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 { // 调用触发 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 { // 获取当前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 { // 调用触发 filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { ...... } return; } // 下面的代码有疑似结束Filter,触发请求函数处理相关的代码。先打个标记,后面探索 // We fell off the end of the chain -- call the servlet instance ...... }
上面就是Filter调用触发的核心代码了,链式触发调用,在SpringCloudGateway和Netty中都有类型的相关代码,看着这种代码模式很经典啊,但细节就后面研究了
经过上面的代码分析,Filter的基本核心代码已经被我们找到了:
其中还有很多疑问点,还有Order排序相关的好像没有找到,感兴趣的可以自行查找下