目录

1.概述

2.Spring Boot的扩展机制之Spring Factories

2.1什么是 SPI机制

2.2 Spring Boot中的SPI机制

2.3 Spring Factories实现原理是什么

2.4 Spring Factories在Spring Boot中的应用

3.用法及配置Bean

3.1 ApplicationContextInitializer

3.2 ApplicationListener

3.3 AutoConfigurationImportListener

3.4 AutoConfigurationImportFilter

3.5 EnableAutoConfiguration

3.6 FailureAnalyzer

3.7 TemplateAvailabilityProvider

3.8 EnvironmentPostProcessor 


1.概述

在 Spring Boot 项目中,怎样将 pom.xml 文件里面添加的依赖中的 bean 注册到 Spring Boot 项目的 Spring 容器中呢?

你可能会首先想到使用 @ComponentScan 注解,遗憾的是 @ComponentScan 注解只能扫描 Spring Boot 项目包内的 bean 并注册到 Spring 容器中,项目依赖包中的 bean 不会被扫描和注册。此时,我们需要使用 @EnableAutoConfiguration 注解来注册项目依赖包中的 bean。而 spring.factories 文件,可用来记录项目包外需要注册的 bean 类名。

使用 spring.factories 文件有什么好处呢?假如我们封装了一个插件,该插件提供给其他开发人员使用。我们可以在 spring.factories 文件中指定需要自动注册到 Spring 容器的 bean 和一些配置信息。使用该插件的开发人员只需少许配置,甚至不进行任何配置也能正常使用。

2.Spring Boot的扩展机制之Spring Factories


写在前面:Spring Boot中有一种非常解耦的扩展机制:Spring Factories。这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的。


2.1什么是 SPI机制


        SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。
简单的总结下java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

2.2 Spring Boot中的SPI机制


在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这种自定义的SPI机制是Spring Boot Starter实现的基础。
 

2.3 Spring Factories实现原理是什么

        spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。
loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。
上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下↓

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);
    }
}

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件。也就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:

com.xxx.interface=com.xxx.classname

如果一个接口希望配置多个实现类,可以使用’,’进行分割。

2.4 Spring Factories在Spring Boot中的应用


在Spring Boot的很多包中都能够找到spring.factories文件,接下来我们以spring-boot包为例进行介绍

在日常工作中,我们可能需要实现一些SDK或者Spring Boot Starter给被人使用时,
我们就可以使用Factories机制。Factories机制可以让SDK或者Starter的使用只需要很少或者不需要进行配置,只需要在服务中引入我们的jar包即可。

3.用法及配置Bean

 spring.factories 文件的用法,以及介绍该文件中可以配置那些 Bean。内容如下:

## Initializers
org.springframework.context.ApplicationContextInitializer=\
com.huangx.springboot.autoconfig.MyApplicationContextInitializer
 
## Application Listeners
org.springframework.context.ApplicationListener=\
com.huangx.springboot.autoconfig.MyApplicationListener
 
## Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
com.huangx.springboot.autoconfig.MyAutoConfigurationImportListener
 
## Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.huangx.springboot.autoconfig.MyConfigurationCondition
 
## Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.huangx.springboot.autoconfig.MyConfiguration
 
## Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.huangx.springboot.autoconfig.MyFailureAnalyzer
 
## Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
com.huangx.springboot.autoconfig.MyTemplateAvailabilityProvider

#后置环境变量处理器
org.springframework.boot.env.EnvironmentPostProcessor=cn.telecom.starter.cloud.LoadProperties

下面将分别介绍各种配置的具体含义:

3.1 ApplicationContextInitializer

该配置项用来配置实现了 ApplicationContextInitializer 接口的类,这些类用来实现上下文初始化。配置如下:

org.springframework.context.ApplicationContextInitializer=\
com.huangx.springboot.autoconfig.MyApplicationContextInitializer

实例代码:

public class MyApplicationContextInitializer implements
        ApplicationContextInitializer<ConfigurableApplicationContext> {
 
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer.initialize() " + applicationContext);
    }
 
}

3.2 ApplicationListener

配置应用程序监听器,该监听器必须实现 ApplicationListener 接口。它可以用来监听 ApplicationEvent 事件。配置如下:

org.springframework.context.ApplicationListener=\
com.huangx.springboot.autoconfig.MyApplicationListener

实例代码:

public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
 
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("MyApplicationListener.onApplicationEvent() " + event);
        if(event instanceof ApplicationStartedEvent) {
            throw new RuntimeException("我故意抛出的错误,仅仅为了触发自定义 MyFailureAnalyzer");
        }
    }
 
}

3.3 AutoConfigurationImportListener

该配置项用来配置自动配置导入监听器,监听器必须实现 AutoConfigurationImportListener  接口。该监听器可以监听 AutoConfigurationImportEvent 事件。配置如下:

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
com.huangx.springboot.autoconfig.MyAutoConfigurationImportListener

实例代码:

public class MyAutoConfigurationImportListener implements AutoConfigurationImportListener {
 
    @Override
    public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
        System.out.println("MyAutoConfigurationImportListener.onAutoConfigurationImportEvent() " + event);
    }
 
}

3.4 AutoConfigurationImportFilter

配置自动配置导入过滤器,过滤器必须实现 AutoConfigurationImportFilter 接口。该过滤器用来过滤那些自动配置类可用,配置信息:


org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.huangx.springboot.autoconfig.MyConfigurationCondition

实例代码:

public class MyConfigurationCondition implements AutoConfigurationImportFilter {
 
    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        System.out.println("MyConfigurationCondition.match() autoConfigurationClasses=" +
                Arrays.toString(autoConfigurationClasses) +
                ", autoConfigurationMetadata=" + autoConfigurationMetadata);
        return new boolean[0];
    }
 
}

3.5 EnableAutoConfiguration

配置自动配置类。这些配置类需要添加 @Configuration 注解,配置如下:


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.huangx.springboot.autoconfig.MyConfiguration

实例代码:

@Configuration
public class MyConfiguration {
 
    public MyConfiguration() {
        System.out.println("MyConfiguration()");
    }
 
}

3.6 FailureAnalyzer

配置自定的错误分析类,该分析器需要实现 FailureAnalyzer 接口。配置信息:

org.springframework.boot.diagnostics.FailureAnalyzer=\
com.huangx.springboot.autoconfig.MyFailureAnalyzer

实例代码:

/**
 * 自定义自己的错误分析器 FailureAnalyzer
 * @author Administrator 2021/4/1 13:14
 * @version 1.0
 */
public class MyFailureAnalyzer implements FailureAnalyzer {
 
    @Override
    public FailureAnalysis analyze(Throwable failure) {
        System.out.println("MyFailureAnalyzer.analyze() failure=" + failure);
        return new FailureAnalysis("MyFailureAnalyzer execute", "test spring.factories", failure);
    }
 
}

运行效果如下图:

3.7 TemplateAvailabilityProvider

配置模板的可用性提供者,提供者需要实现 TemplateAvailabilityProvider 接口,配置如下:


org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
com.huangx.springboot.autoconfig.MyTemplateAvailabilityProvider

实例代码:

/**
 * 验证指定的模板是否支持
 * @author Administrator 2021/4/1 13:22
 * @version 1.0
 */
public class MyTemplateAvailabilityProvider implements TemplateAvailabilityProvider {
 
    @Override
    public boolean isTemplateAvailable(String view, Environment environment,
                                       ClassLoader classLoader, ResourceLoader resourceLoader) {
        System.out.println("MyTemplateAvailabilityProvider.isTemplateAvailable() view=" +
                view + ", environment=" + environment + ", classLoader=" + classLoader +
                "resourceLoader=" + resourceLoader);
        return false;
    }
 
}

3.8 EnvironmentPostProcessor 

环境后置处理器

org.springframework.boot.env.EnvironmentPostProcessor=cn.telecom.starter.cloud.LoadProperties

实例代码:

public class LoadProperties implements EnvironmentPostProcessor {

    /**
     * 文件名
     */
    private static final String FILE_NAME = "test-starter-cloud.properties";

    /**
     * 版本锁
     */
    private static int version = 0;


    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // 通过版本号控制解决启动服务时加载两次的问题
        if (version++ == 0) {
            System.out.println("动态加载自定义配置:" + getClass().getName());
            MutablePropertySources sources = environment.getPropertySources();
            // 加载指定的配置文件(效率较高,但需要指定文件)
            loadPropertySource(sources, FILE_NAME);
        }
    }

    /**
     * 加载配置文件资源
     *
     * @param sources
     * @param file
     */
    private void loadPropertySource(MutablePropertySources sources, String file) {
        try {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            InputStream is = classLoader.getResourceAsStream(file);

            if (is != null) {
                Properties properties = new Properties();
                properties.load(is);
                PropertiesPropertySource propertySource = new PropertiesPropertySource("dynamic", properties);
                sources.addLast(propertySource);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

文章内容来源:

Spring Boot的扩展机制之Spring Factories_伊成的博客-CSDN博客_spring.factories文件

Spring Boot 你不得不会的 spring.factories 配置 - 人人编程网

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐