写点什么

Spring 源码解析 -- SpringWeb 过滤器 Filter 解析

用户头像
关注
发布于: 23 分钟前

简介

在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如 Filter。本篇探索 Filter 初始化、请求处理等相关代码。

前言

说先简单的定义相关的测试代码:


启动类:


import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan@SpringBootApplicationpublic 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.*;
@RestControllerpublic 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 处理如下:


  • / : 两个 Filter 都不触发

  • /test1 : 触发 MyFilter1

  • /test2 : 触发 MyFilter2


符合我们的使用预期,接下来我们到源码中探索:


  • 1.Filter 是如何初始化的

  • 2.Filter 是如何对应相关的 URL 请求的

源码解析

探索时是直接在 MyFilter 类打上断点,一步步探索堆栈,得到的相关源码如下

Filter 初始化

首先是找到 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)); } }
}
复制代码


在上面的代码中,我们看到了拦截路径的配置有两个方式:


  • servletNames

  • 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 的匹配添加


下面的代码是核心的 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 匹配添加的核心代码,值得注意的点有下面几个:


  • 会被匹配添加两次

  • 匹配有下面三种方式:

  • matchDispatcher(filterMap, dispatcher)

  • matchFiltersURL(filterMap, requestPath)

  • matchFiltersServlet(filterMap, servletName)


这里就有下面两点疑问了:


  • 为啥需要将两次匹配分开:是为了前后 Filter 区分?

  • 两次 Filter 循环匹配,好像就是匹配路径 requestPath 和匹配 ServletName 的区别,两者有何不同,为啥需要分开?


关于上面的疑问目前我也没找到确定的线索,后面的探索中,应该能把它补上

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 的基本核心代码已经被我们找到了:


  • 1.Filter 的初始化:在应用程序启动时,进行 Filter 的初始化

  • 2.Filter 的匹配添加:对于不用的请求路径,会匹配生成不同的 Filter 链路

  • 其中有用缓存吗:经过实验,每次请求都会进行匹配

  • 3.链式调用处理


其中还有很多疑问点,还有 Order 排序相关的好像没有找到,感兴趣的可以自行查找下

参考链接

发布于: 23 分钟前阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
Spring 源码解析 -- SpringWeb过滤器Filter解析