1. 程式人生 > >Spring Boot 擴展點應用之工廠加載機制

Spring Boot 擴展點應用之工廠加載機制

rop logger dfa 控制 ted ext.get 並保存 log iat

Spring 工廠加載機制,即 Spring Factories Loader,核心邏輯是使用 SpringFactoriesLoader 加載由用戶實現的類,並配置在約定好的META-INF/spring.factories 路徑下,該機制可以為框架上下文動態的增加擴展。
該機制類似於 Java SPI,給用戶提供可擴展的鉤子,從而達到對框架的自定義擴展功能。

核心實現類 SpringFactoriesLoader

SpringFactoriesLoaderSpring 工廠加載機制的核心底層實現類。它的主要作用是 從 META-INF/spring.factories 路徑下加載指定接口的實現類。該文件可能存在於工程類路徑下或者 jar 包之內,所以會存在多個該文件。

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

SpringFactoriesLoader loadFactories load 並從 FACTORIES_RESOURCE_LOCATION文件中實例化給定類型的工廠實現類。 spring.factories 文件必須采用 Properties 格式,其中鍵是接口或抽象類的完全限定*名稱,值是逗號分隔的實現類名稱列表。例如:

該文件的格式,Key 必須為接口或抽象類的全限定名,value 為 具體的實現類,多個以 逗號分隔。類似如下配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,org.springframework.boot.context.ContextIdApplicationContextInitializer,org.springframework.boot.context.config.DelegatingApplicationContextInitializer,org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

從該文件中我們可以看到,其中 ApplicationContextInitializer 為父類,value為實現類,以逗號分隔。

SpringFactoriesLoader 源碼分析

Spring Boot 完成自動裝配的核心之一就是工廠加載機制。我們以 Spring Boot 的自動裝配為例來分析。如果要開啟 Spring 的自動裝配功能,會使用 @EnableAutoConfiguration 這個註解,這個註解會 Import AutoConfigurationImportSelector 這個類。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector 中有一個方法就是加載 EnableAutoConfigurationkey 的實現配置類。
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader)
}
SpringFactoriesLoader loadFactories 加載 所有以 factoryClassKey 的實現類
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        // 省略部分前置判斷和 logger 代碼 
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        //根據當前接口類的全限定名作為key,從loadFactoryNames從文件中獲取到所有實現類的全限定名
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        
        List<T> result = new ArrayList<>(factoryNames.size());
        //實例化所有實現類,並保存到 result 中返回。
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
調用 loadSpringFactoriesMETA-INF/spring.factories文件中進行加載

從文件中讀取接口和實現類的邏輯,返回 Map<String, List<String>>

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
        try {
            //FACTORIES_RESOURCE_LOCATION --> META-INF/spring.factories
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            //一Key多值 Map,適合上文提到的一個接口多個實現類的情形。
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    //以逗號進行分割,得到List的實現類全限定名集合
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            //返回
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
}

總結

上面通過以 Spring Boot 的自動裝配為例,我們分析了 Spring 工廠加載機制的整個過程,重點分析了SpringFactoriesLoader類。通過這樣的機制,我們可以十分的方便的為框架提供各式各樣的擴展插件,我們可以自己定義自己的組件的自動裝配配置類,然後通過工廠加載機制讓 Spring 進行加載並得到自動裝配。

工廠加載機制的應用 ApplicationContextInitializer

ApplicationContextInitializer 是在 Spring Boot 或者 Spring Mvc 啟動過程中調用的。具體時機為Spring 應用上下文 refresh 之前(調用 refresh 方法)。

ApplicationContextInitializer 主要提供應用上下文未refresh之前的擴展,這時候可以對 ConfigurableApplicationContext 進行一些擴展處理等。

自定義一個類,實現 ApplicationContextInitializer ,並重寫 initialize 方法:

@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("=================> applicationContext: " + applicationContext.getId());
    }
}

啟動 Spring Boot 程序,我們可以看到在 refresh 之前,會在控制臺打印上面這句話。

  • 另外的實現方式:
    • application.properties添加配置方式:
context.initializer.classes=com.maple.spring.initializer.AfterHelloWorldApplicationContextInitializer

對於這種方式是通過 DelegatingApplicationContextInitializer 這個初始化類中的 initialize 方法獲取到 application.propertiescontext.initializer.classes 對應的實現類,並對該實現類進行加載。

3.在 SpringApplication 中直接添加

public static void main(String[] args) {
        new SpringApplicationBuilder(SpringBootDemo3Application.class)
                .initializers(new AfterHelloWorldApplicationContextInitializer())
                .run(args);
    }
}

Spring Boot 使用 工廠機制加載 ApplicationListener 實現類

# Application Listeners
org.springframework.context.ApplicationListener=org.springframework.boot.ClearCachesApplicationListener,org.springframework.boot.builder.ParentContextCloserApplicationListener,org.springframework.boot.context.FileEncodingApplicationListener,org.springframework.boot.context.config.AnsiOutputApplicationListener,org.springframework.boot.context.config.ConfigFileApplicationListener,org.springframework.boot.context.config.DelegatingApplicationListener,org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,org.springframework.boot.context.logging.LoggingApplicationListener,org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

總結

工廠加載機制是 Spring 動態加載實現類的一種方式,提前在擴展類中寫好對應自動配置類,我們可以完成自動裝配的效果。Spring Boot 自動裝配模塊其中的loader 自動配置實現類就是基於此實現的。
Spring Boot 的一些新特性幾乎用到的都是 Spring Framework 的核心特性。因此學習 Spring Boot ,歸根結底就是學習 Spring Framework 核心。它是所有 Spring 應用的基石,所以我們應該從上至下,由淺入深來進行學習和分析。

Spring Boot 擴展點應用之工廠加載機制