SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)

简介: SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)


概述

SpringBoot 摒弃了繁琐的 xml 配置的同时,提供了几种注册组件:ServletRegistrationBean, FilterRegistrationBean,ServletListenerRegistrationBean,DelegatingFilterProxyRegistrationBean,用于注册自对应的组件,如过滤器,监听器等。

本篇来分析过滤器注册组件FilterRegistrationBean,理解实现原理,有助于平时开发遇到对应的问题,能够快速的分析和定位。 内容涉及以下几点:

  • FilterRegistrationBean加载机制
  • FilterChain责任链构造方式
  • 自定义FilterChain

一 FilterRegistrationBean 加载机制

先来看一下该类 uml:

Springboot 1.x 版本:

Springboot 2.x 版本:

首先,ServletContextInitializer是 Servlet 容器初始化的时候,提供的初始化接口。FilterRegistrationBean最终实现了ServletContextInitializer,所以,Servlet 容器初始化会获取并触发所有的FilterRegistrationBean实例化。 两个版本中变化不是很大,只是SpringBoot 2.x版本中,将AbstractFilterRegistrationBean中的注册逻辑提取到DynamicRegistrationBean抽象类中。

来看一下源码。 Spring 刷新容器会执行onRefresh

跟进该方法:

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
      //获取指定的 Servlet类型
      ServletWebServerFactory factory = getWebServerFactory();
      //指定 ServletContextInitializer 触发逻辑
      this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
      try {
        getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
        throw new ApplicationContextException("Cannot initialize servlet context",
            ex);
      }
    }
    initPropertySources();
  }
复制代码

上述首先获取当前 Servlet 容器类型,本篇以 Jetty 为例进行分析。 上面有一个参数比较重要: this.webServer = factory.getWebServer(getSelfInitializer()); 这里传入了一个回调函数 getSelfInitializer():

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
  }
复制代码

这是用来获取所有的ServletContextInitializer并实例化的回调函数,什么时候触发呢?

当容器启动时,会执行callInitializers,通过onStartup会触发回调函数。回调函数是定义在ServletWebServerApplicationContext中的selfInitialize方法,跟进该方法:

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
        beanFactory);
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
        getServletContext());
    existingScopes.restore();
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
        getServletContext());
    //这里便是获取所有的 ServletContextInitializer 实现类,会获取所有的注册组件
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
      beans.onStartup(servletContext);
    }
  }
复制代码

跟进上面的getServletContextInitializerBeans方法:

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
  }
复制代码

ServletContextInitializerBeans对象是对ServletContextInitializer的一种包装,构造函数如下:

public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    this.initializers = new LinkedMultiValueMap<>();
    //获取所有的 ServletContextInitializer
    addServletContextInitializerBeans(beanFactory);
    addAdaptableBeans(beanFactory);
    List<ServletContextInitializer> sortedInitializers = new ArrayList<>();
    //监听器,过滤器,以及 servlet的排序逻辑
    this.initializers.values().forEach((contextInitializers) -> {
      AnnotationAwareOrderComparator.sort(contextInitializers);
      sortedInitializers.addAll(contextInitializers);
    });
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
  }
复制代码

可以看到其构造函数中执行了addServletContextInitializerBeans方法,该方法传入了 beanFactory,也就是从容器中获取所有的ServletContextInitializer,并进行实例化,然后进行排序。来看看具体是如何获取的?

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    //指定ServletContextInitializer.class 类型
    for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
        beanFactory, ServletContextInitializer.class)) {
      //添加到具体的集合中
      addServletContextInitializerBean(initializerBean.getKey(),
          initializerBean.getValue(), beanFactory);
    }
  }
复制代码

addServletContextInitializerBean方法会判断具体实现类的类型,也就是开头提到的几种注册组件:

private void addServletContextInitializerBean(String beanName,
      ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
    // Servlet注册组件
    if (initializer instanceof ServletRegistrationBean) {
      Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
      addServletContextInitializerBean(Servlet.class, beanName, initializer,
          beanFactory, source);
    }
    //过滤器注册组件
    else if (initializer instanceof FilterRegistrationBean) {
      Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
      addServletContextInitializerBean(Filter.class, beanName, initializer,
          beanFactory, source);
    }
    else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
      String source = ((DelegatingFilterProxyRegistrationBean) initializer)
          .getTargetBeanName();
      addServletContextInitializerBean(Filter.class, beanName, initializer,
          beanFactory, source);
    }
    else if (initializer instanceof ServletListenerRegistrationBean) {
      EventListener source = ((ServletListenerRegistrationBean<?>) initializer)
          .getListener();
      addServletContextInitializerBean(EventListener.class, beanName, initializer,
          beanFactory, source);
    }
    else {
      addServletContextInitializerBean(ServletContextInitializer.class, beanName,
          initializer, beanFactory, initializer);
    }
  }
复制代码

上述逻辑主要是对容器中获取的ServletContextInitializer实现类进行分类,存入对应的组件集合当中。以此实现各自组件的功能。

继续主流程,来看一下过滤器的注册逻辑。如下图:

上述方法获取所有的ServletContextInitializer,进行循环注册,跟进onStartup方法:

RegistrationBean类提供了一个模板方法:register,对应的注册组件执行各自的注册逻辑。这里来看一下过滤器注册组件的实现:

@Override
  protected Dynamic addRegistration(String description, ServletContext servletContext) {
    Filter filter = getFilter();
    return servletContext.addFilter(getOrDeduceName(filter), filter);
  }
复制代码

上述方法获取过滤器,并通过ServletContext注入到Servlet容器中,继续跟进addFilter方法:

@Override
        public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
        {
            //......
            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
            //判断filter是否已注册
            FilterHolder holder = handler.getFilter(filterName);
            if (holder == null)
            {
                //new filter
                //创建一个新的holder,注入到ServletHandler中
                holder = handler.newFilterHolder(Source.JAVAX_API);
                holder.setName(filterName);
                //将filter设置到holder中
                holder.setFilter(filter);
                handler.addFilter(holder);
                return holder.getRegistration();
            }
            if (holder.getClassName()==null && holder.getHeldClass()==null)
            {
                //preliminary filter registration completion
                holder.setHeldClass(filterClass);
                return holder.getRegistration();
            }
            else
                return null; //existing filter
        }
复制代码

至此,自定义的 Filter 就注入到了 Servlet 容器中。

注意:ServletWebServerApplicationContext 是SpringBoot 2.x版本中的命名,对应的是1.x版本中的EmbeddedWebApplicationContext。

二 FilterChain责任链构造方式

FilterChain 采用了责任链模式,也是责任链模式的一种典型使用方式。类似于 Pipeline 模式。

Jetty中的 FilterChain 对象默认是懒加载的形式,只有第一次请求进来的时候才会初始化,如下图:

请求进来,首先会判断_filterMappings是否为空,不为空则获取FilterChain对象。 继续来看getFilterChain方法:

protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
    {
        String key=pathInContext==null?servletHolder.getName():pathInContext;
        int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
        //通过 url,从缓存中获取 FilterChain
        if (_filterChainsCached && _chainCache!=null)
        {
            FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
            if (chain!=null)
                return chain;
        }
       //如果未获取到,则构造一个FilterChain对象
        FilterChain chain = null;
        //判断是否开启了缓存
        if (_filterChainsCached)
        {
            if (filters.size() > 0)
                chain= new CachedChain(filters, servletHolder);
            final Map<String,FilterChain> cache=_chainCache[dispatch];
            final Queue<String> lru=_chainLRU[dispatch];
                // Do we have too many cached chains?
                //判断缓存中是否有了太多的FilterChain,如果大于最大长度,进行删除。
                while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
                {
                    // The LRU list is not atomic with the cache map, so be prepared to invalidate if
                    // a key is not found to delete.
                    // Delete by LRU (where U==created)
                    String k=lru.poll();
                    if (k==null)
                    {
                        cache.clear();
                        break;
                    }
                    cache.remove(k);
                }
                cache.put(key,chain);
                lru.add(key);
        }
        else if (filters.size() > 0)
            chain = new Chain(baseRequest,filters, servletHolder);
        return chain;
    }
复制代码

Jetty 实现了一个对 FilterChain 缓存的功能,以 URL为key,每次请求进来,根据 URL 获取对应的过滤器链。 另外实现了 LRU 算法,当缓存长度超过最大限度时,清理掉最早未使用的键值对。

但是很多请求下,不同的 URL 获取的过滤器链是一样的,所以这里没必要开启缓存。Jetty提供了_filterChainsCached进行设置,上述代码也是通过此变量进行判断。 默认为 true,默认使用了缓存。

需要注意一点:只有开启 FilterChain 缓存,创建CachedChain对象,才会采用责任链模式。 如果创建的是Chain对象,则直接遍历所有过滤器处理。

来看一下CachedChain构造方法,责任链相关代码:

CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
        {
            if (filters.size()>0)
            {
                _filterHolder=filters.get(0);
                filters.remove(0);
                //递归处理
                _next=new CachedChain(filters,servletHolder);
            }
            else
                _servletHolder=servletHolder;
        }
复制代码

代码比较简洁,对构造方法进行递归处理,创建CachedChain链表,最终生成的对象如下形式:

characterEncodingFilter->hiddenHttpMethodFilter->httpPutFormContentFilter->requestContextFilter->webRequestLoggingFilter->authenticationFilter->traceFilter->applicationContextIdFilter->Jetty_WebSocketUpgradeFilter
复制代码

三 自定义 FilterChain

以下实例代码提供了两种方式创建FilterChain,构造方法递归实现和普通方法递归实现。

定义一个 Filter接口:

public interface MyFilter {
    String getName();
    void execute(FilterChain filterChain);
}
复制代码

定义两个实现类:

public class MyFilters{
    /**
     * 定义两个个Myfilter
     *
     */
    public static class MyFilter1 implements  MyFilter{
        @Override
        public String getName() {
            return "myFilter1";
        }
        @Override
        public void execute(FilterChain filterChain) {
            System.out.println(getName()+"before...");
            if (null != filterChain) {
                filterChain.doFilter(filterChain);
            }
            System.out.println(getName()+"after...");
        }
    }
    public static class MyFilter2 implements  MyFilter{
        @Override
        public String getName() {
            return "myFilter2";
        }
        @Override
        public void execute(FilterChain filterChain) {
            System.out.println(getName()+"before...");
            if (null != filterChain) {
                filterChain.doFilter(filterChain);
            }
            System.out.println(getName()+"after...");
        }
    }
}
复制代码

FilterChain 对象:

@Data
public class FilterChain {
    private MyFilter currentFilter;
    private FilterChain next;
    private List<MyFilter> filters;
    /**
     *
     * 递归实现责任链
     */
    public FilterChain(MyFilter myFilter) {
        this.currentFilter = myFilter;
    }
    /**
     *
     * 模拟 SpringBoot Jetty 中的 fiterChain 责任链实现机制
     */
    public FilterChain(List<MyFilter> filters) {
        if (filters.size() > 0) {
            this.currentFilter = filters.get(0);
            filters.remove(0);
            this.next = new FilterChain(filters);
        }
    }
    public void doFilter(FilterChain filterChain) {
        MyFilter currentFilter = filterChain.getCurrentFilter();
        if (null != currentFilter) {
            currentFilter.execute(filterChain.next);
        }
    }
}
复制代码

通过构造方法递归实现:

public class FilterChainBuilder {
    static List<MyFilter> filters;
    public static FilterChain buildFilterChainBuild(List<MyFilter> myFilters){
        filters = myFilters;
        return FilterChainInstanceFactory.FILTER_CHAIN;
    }
    private  static class FilterChainInstanceFactory{
        final static FilterChain FILTER_CHAIN = new FilterChain(filters);
    }
}
复制代码

通过普通方法递归实现:

public class FilterChainBuilder2 {
    public static FilterChain buildFilterChain(List<MyFilter> filters) {
        if (CollectionUtils.isEmpty(filters)) {
            return null;
        }
        MyFilter currentFilter = filters.get(0);
        FilterChain filterChain2 = new FilterChain(currentFilter);
        filters.remove(0);
        if (filters.size() > 0) {
            filterChain2.setNext(buildFilterChain(filters));
        }
        return filterChain2;
    }
}
复制代码

具体代码 Github: github.com/admin801122…

总结

SpringBoot 在加载 Servlet 容器时,会获取扩展接口ServletContextInitializer的所有实现类。过滤器,监听器等注册组件正是实现了该接口,从而完成了对应各自注册的机制。另外过滤器链采用了 LRU 算法实现了缓存机制,并通过在 FilterChain 构造方法中递归实现了责任链机制。



目录
相关文章
|
XML Java 数据格式
Springboot中自定义组件
Springboot中自定义组件
256 1
|
Dubbo Java 应用服务中间件
微服务框架(十六)Spring Boot及Dubbo zipkin 链路追踪组件埋点
此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。 本文第一部分为调用链、OpenTracing、Zipkin和Jeager的简述;第二部分为Spring Boot及Dubbo zipkin 链路追踪组件埋点
|
Java 应用服务中间件 容器
SpringBoot之Web原生组件注入
SpringBoot之Web原生组件注入
245 1
|
Java 数据库连接 Spring
SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar 源码分析 (十)
SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar 源码分析 (十)
407 0
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
459 0
|
Java 数据库 数据安全/隐私保护
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
1576 1
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
1308 2
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
4813 14
|
前端开发 Java 数据库
玩转springboot之springboot注册servlet
在Spring Boot中注册Servlet非常灵活,可以通过 `@WebServlet`注解快速注册,也可以通过 `ServletRegistrationBean`进行细粒度控制。通过这两种方式,可以满足各种场景下的需求,确保应用能够高效处理HTTP请求。
1289 14
|
SQL JavaScript 前端开发
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
这篇文章详细介绍了如何在Vue.js中使用分页组件展示从数据库查询出来的数据,包括前端Vue页面的表格和分页组件代码,以及后端SpringBoot的控制层和SQL查询语句。
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)

热门文章

最新文章