Spring Boot 扩展点应用之工厂加载机制

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 应用的基石,所以我们应该从上至下,由浅入深来进行学习和分析。

原文地址:https://www.cnblogs.com/leihuazhe/p/9751836.html

时间: 2024-11-07 23:43:58

Spring Boot 扩展点应用之工厂加载机制的相关文章

【Spring源码分析】非懒加载的Bean实例化过程(下篇)

doCreateBean方法 上文[Spring源码分析]非懒加载的Bean实例化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireCapableBeanFactory的doCreateBean方法代码: 1 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[]

Spring懒加载机制

Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean 花费大量的空间存储bean,但这些bean可能很久都用不上,这种在启动时在时间和空间上的浪费显得非常的不值得. 所以Spring提供了懒加载机制.所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗. 懒加载

Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制.当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类.所以在程序中并没有直接指定使用接口的哪个实现,而是在外部进行装配. 要想了解 Dubbo 的设计与实现,其中 Dubbo SPI 加载机制是必须了解的,在 Dubbo 中有大量功能的实现都是基于 Dubbo SPI 实现解耦,同时也使得 Dubbo 获得如此好的可扩展性. Java SPI 通过完成一个 Java SPI 的操作来了解它的机

浅析dex文件加载机制

我们可以利用DexClassLoader来实现动态加载dex文件,而很多资料也只是对于DexClassLoader的使用进行了介绍,没有深入讲解dex的动态加载机制,我们就借助于Android4.4的源码来探索.先从一个简单的动态加载dex文件开始 具体实现细节可以参考这篇文章AndroidDex数据动态加载技术 Android4.4的源码在百度网盘分享: Android 4.4源码下载 先是我们要封装到text.jar文件中的很简单的调用函数,只是简单的产生Toast: /* * 对外接口 *

jvm系列(一):java类的加载机制

java类的加载机制 原文:http://www.cnblogs.com/ityouknow/p/5603287.html 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口. 类加载器并不需要等到某个

PHP的类文件自动加载机制

搜集于网络,学习php的类的自动加载机制,在实际大型项目中其重要性尤为突出. PHP的类自动加载机制 在PHP开发过程中,如果希望从外部引入一个class,通常会使用include和require方法,去把定义这个class的文件包含进来.这个在小规模开发的时候,没什么大问题.但在大型的开发项目中,这么做会产生大量的require或者include方法调用,这样不因降低效率,而且使得代码难以维护,况且require_once的代价很大. 在PHP5之前,各个PHP框架如果要实现类的自动加载,一般

Java ClassLoader加载机制

一.体系结构(自上向下) 1.Bootstrap ClassLoader(BootStrapClassLoader) --- 启动类加载器或者叫引导类加载器,加载jdk核心的APIs,这些APIs一般位于jdk_home/lib下:它是一个本地接口,所以不能从java代码中得到它的信息.例如, log(java.lang.String.class.getClassLoader())得到的是null. 2.Extension ClassLoader(Launcher$ExtClassLoader)

java加载机制整理

本文是根据李刚的<疯狂讲义>作的笔记,程序有的地方做了修改,特别是路径,一直在混淆,浪费了好多时间!!希望懂的同学能够指导本人,感激尽............ 1.jvm 和 类的关系 当调用 java命令运行一个java程序时,必会启动一个jvm即java虚拟机.(5.6.处有联系!!)该java程序的所有线程,变量都处于jvm中,都使用该jvm的内存区 jvm终止的情况:1.程序自然运行结束2.遇到System.exit();Runtime.getRuntime.exit();3.遇到未捕

PHP autoload与spl_autoload自动加载机制的深入理解

PHP autoload与spl_autoload自动加载机制的深入理解 作者: 字体:[增加 减小] 类型:转载 时间:2013-06-05我要评论 本篇文章是对PHP中的autoload与spl_autoload自动加载机制进行了详细的分析介绍,需要的朋友参考下 PHP autoload机制详解(1) autoload机制概述在使用PHP的OO模式开发系统时,通常大家习惯上将每个类的实现都存放在一个单独的文件里,这样会很容易实现对类进行复用,同时将来维护时也很便利.这 也是OO设计的基本思想