这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口。

SPI是供给框架开发者以方便的对框架进行额外拓展的;API是提供给框架使用者的,使用者可以实现API接口来定义自己的功能组件。

很多框架都使用了java的SPI机制,如java.sql.Driver的SPI实现(MySQL驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架;

说到Spring,那么就离不开Bean的概念,SpringCloud的SPI实质上也是将一些Bean引入Spring容器中,也就是将netflix的配置类以Bean的形式整合进SpringCloud中

我们以Eureka为例

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
    <version>1.4.4.RELEASE</version>
</dependency>

以1.4.4.RELEASE版本为例说明SpringCloud如何通过SPI机制整合Eureka。

在spring-cloud-starter-eureka-server包下”/META-INF“有一个”spring.factories“文件:

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。

原理是这样,接着往下看。

一般在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容器中。

通过这种形式,当改变版本时,可以很容易的修改引入的配置类,也就是更换拓展的组件,Springcloud-Eureka在近几个版本里有显著的配置改变。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐