SpringBoot2 | SpingBoot FilterRegistrationBean 註冊組件 | FilterChain 責任鏈源碼分析(九)

微信公衆號:吉姆餐廳ak 學習更多源碼知識,歡迎關注。 git

在這裏插入圖片描述


SpringBoot2 | SpringBoot啓動流程源碼分析(一)github

SpringBoot2 | SpringBoot啓動流程源碼分析(二)web

SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)算法

SpringBoot2 | SpringBoot Environment源碼分析(四)spring

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)緩存

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)springboot

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)bash

SpringBoot2 | Spring AOP 原理源碼深度剖析(八)微信

SpringBoot2 | SpingBoot FilterRegistrationBean 註冊組件 | FilterChain 責任鏈源碼分析(九)app

SpringBoot2 | BeanDefinition 註冊核心類 ImportBeanDefinitionRegistrar (十)

SpringBoot2 | Spring 核心擴展接口 | 核心擴展方法總結(十一)


概述

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 構造方法中遞歸實現了責任鏈機制。

相關文章
相關標籤/搜索