天天看點

從Java SPI機制實作到Spring Cloud SPI擴充

Java SPI實作

可參考上一篇文章:

https://blog.csdn.net/shang_xs/article/details/86560469

Spring Cloud SPI 實作

1. Spring Cloud SPI

SPI英文為Service Provider Interface單從字面可以了解為Service提供者接口,正如從SPI的名字去了解SPI就是Service提供者接口;我對SPI的定義:提供給服務提供廠商與擴充架構功能的開發者使用的接口。

SPI是供給架構開發者以友善的對架構進行額外拓展的;API是提供給架構使用者的,使用者可以實作API接口來定義自己的功能元件。

2. 其他架構SPI思想

很多架構都使用了java的SPI機制,如java.sql.Driver的SPI實作(MySQL驅動、oracle驅動等)、common-logging的日志接口實作、dubbo的擴充實作等等架構;

說到Spring,那麼就離不開Bean的概念,SpringCloud的SPI實質上也是将一些Bean引入Spring容器中,也就是将netflix的配置類以Bean的形式整合進SpringCloud中。

我們以Eureka為例

3. Spring Cloud Eureka

3.1 pom檔案

<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-eureka-server</artifactId>

    <version>1.4.4.RELEASE</version>

</dependency>
           

3.2 spring.factories檔案

以1.4.4.RELEASE版本為例說明SpringCloud如何通過SPI機制整合Eureka。

在spring-cloud-starter-eureka-server包下”/META-INF“有一個”spring.factories“檔案:

其内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\

org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\

org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\

org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\

org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\

org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
           

其内部有兩個鍵值對,

org.springframework.boot.autoconfigure.EnableAutoConfiguration就是注解@EnableAutoConfiguration的類全名。
org.springframework.cloud.bootstrap.BootstrapConfiguration就是注解@BootstrapConfiguration的類全名,這兩個注解都是Spring的啟動注解,一個引導父容器,一個引導子容器(mvc容器)。
           

到這裡基本就有眉目了,兩個注解對應不同的Class,也就是說可以通過這兩個注解來将相應的Class引入Spring容器,定義成Bean。

3.3 原理實作

一般在Springboot的啟動類上會有這個注解:@SpringBootApplication

@EnableEurekaServer @SpringBootApplication public class SpringCloudEurekaServerApplication {

public static void main(String[] args) {
    SpringApplication.run(SpringCloudEurekaServerApplication.class, args);
}
} Spring在初始化時,會逐層的解析定義的注解,看@SpringBootApplication的内部結構:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {

......
           

其上辨別有@EnableAutoConfiguration注解,我們接着看這個注解的内部結構:

@SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
 * Exclude specific auto-configuration classes such that they will never be applied.
 * @return the classes to exclude
 */
Class<?>[] exclude() default {};

/**
 * Exclude specific auto-configuration class names such that they will never be
 * applied.
 * @return the class names to exclude
 * @since 1.3.0
 */
String[] excludeName() default {};
           

@EnableAutoConfiguration 内部有兩個可定義屬性,從名稱上看是排除類,他們的用法就是排除這個注解對應在spring.factories裡的鍵值對。 也就是我通過@EnableAutoConfiguration引入的配置類,可以通過其内部屬性來排除不需要的,這樣能夠提供一定的靈活性。代碼實作在後面會講。

@EnableAutoConfiguration上有一個@Import注解,引入了EnableAutoConfigurationImportSelector,看其代碼:

public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {

@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
        return getEnvironment().getProperty(
                EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
                true);
    }
    return true;
}
}
           
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 根據@EnableAutoConfiguration 注解資料來擷取候選的配置類
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        // 擷取要排除的類名稱
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        // 從候選類中移除那些待排除的
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
        throw new IllegalStateException(ex);
    }
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    // 通過SpringFactoriesLoader來擷取指定Class對應的配置類名稱
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
           

AutoConfigurationImportSelector間接實作了ImportSelector,這個類的作用就是可選擇的引入一個Class,将這個Class注冊到Spring容器中。

在其selectImports(…)方法中,最終通過SpringFactoriesLoader來擷取EnableAutoConfiguration.class對應的配置類名稱,看SpringFactoriesLoader的擷取邏輯:

public abstract class SpringFactoriesLoader {

/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files.
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

/**
 * Load the fully qualified class names of factory implementations of the
 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
 * class loader.
 * @param factoryClass the interface or abstract class representing the factory
 * @param classLoader the ClassLoader to use for loading resources; can be
 * {@code null} to use the default
 * @see #loadFactories
 * @throws IllegalArgumentException if an error occurs while loading factory names
 */
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
           

在SpringFactoriesLoader 的loadFactoryNames(…)方法中,擷取根路徑下 “META-INF/spring.factories“檔案,然後将檔案内容解析成Properties對象,擷取指定類名對應的字元串,然後用”,“拆分字元串,将獲得的字元串數組加入集合中。

也就是說在引入SpringCloud-Eureka的Jar包後,通過@EnableAutoConfiguration 注解引入其對應的字元串:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
           

然後拆分字元串,得到對應的配置類名稱,然後移除@EnableAutoConfiguration 裡指定排除的類名,最後将剩下的類注冊入Spring容器中。

通過這種形式,當改變版本時,可以很容易的修改引入的配置類,也就是更換拓展的元件。

更多擴充實作參見GitHub:https://github.com/dwyanewede/project-learn

Spring Boot擴充點加載:com.learn.demo.spi.ISpi

Dubbo擴充點加載:com.learn.demo.dubbo.extension.MyExtension

更多技術文檔

https://blog.csdn.net/shang_xs/article/details/86560469

https://blog.csdn.net/shang_xs/article/details/86560691

繼續閱讀