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 扩展点应用之工厂加载机制的更多相关文章

  1. Spring Boot 启动(二) Environment 加载

    Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...

  2. spring boot找不到或无法加载主类 io.renren.RenrenApplication

    spring boot找不到或无法加载主类 io.renren.RenrenApplication 出现问题: spring boot 项目以前一直是好好的,用mvn clean package 打包 ...

  3. Spring IOC - 控制反转(依赖注入) - 懒加载机制

    懒加载机制 Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少的时间问题不大,但一旦bean非常多时,Spring需要在启动的过程中 ...

  4. spring boot 使用不同的profile来加载不同的配置文件

    在开发过程之中,经常需要在开发和测试环境中进行互相切换,当切换的同时需要加载相应的配置文件,因此要经常 性的对配置文件进行相应的修改,长此以往感到十分痛苦.如果能针对开发和测试环境分别建两个不同的配置 ...

  5. spring boot 学习入门篇【spring boot项目的搭建以及如何加载jsp界面】

    [ 前言]  Spring Boot 简介:Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置, ...

  6. Spring Boot 2.4.0正式发布,全新的配置文件加载机制(不向下兼容)

    千里之行,始于足下.关注公众号[BAT的乌托邦],有Spring技术栈.MyBatis.JVM.中间件等小而美的原创专栏供以免费学习.分享.成长,拒绝浅尝辄止.本文已被 https://www.you ...

  7. 「新特性」Spring Boot 全局懒加载机制了解一下

    关于延迟加载 在 Spring 中,默认情况下所有定的 bean 及其依赖项目都是在应用启动时创建容器上下文是被初始化的.测试代码如下: @Slf4j @Configuration public cl ...

  8. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

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

  9. Spring源码分析:非懒加载的单例Bean初始化过程(下)

    上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...

随机推荐

  1. CentOS 6 安装配置JDK+tomcat环境

    1.安装OpenJDK 这里安装的OpenJDK,是开源版本的JDK,我们平时自己电脑上安装的是 Sun JDK(也叫Oracle JDK),OpenJDK可以看作Sun JDK的精简版. 如果想安装 ...

  2. Three.js开发指南---粒子和粒子系统(第七章)

    使用粒子可以很容易的创建很多细小的物体,例如雨滴雪花等 本章主要内容: 1 使用ParticleBasicMaterial(基础粒子材质)来创建和设计粒子 2 使用ParticleSystem来创建一 ...

  3. python-桥接模式

    源码地址:https://github.com/weilanhanf/PythonDesignPatterns 说明: 有些类在功能设计上要求,自身包含两个或两个以上变化的因素,即该类在二维或者多维上 ...

  4. Intellij IDEA 使用GitHub+Git

    1.配置Git路径 打开Settings(File-->Settings) --> 在搜索栏内输入git,回车跳转到Git配置页面 --> 将git的运行路径填入Path to Gi ...

  5. MySQL入门详解(一)---mysql的语言

    MySQL语言分为:DCL(数据库控制语言).DDL(数据库定义语言).DQL(数据库查询语言).DML(数据库操作语言),这一节我们先从mysql的语言开始. DCL:数据库控制语言,用来设置数据库 ...

  6. dubbo 图片服务器(FastDFS) redis solr ActiveMQ等简单配置使用

    一.dubbo 项目基于soa的架构,表现层和服务层是不同的工程.所以要实现商品列表查询需要两个系统之间进行通信. 1.1如何实现远程通信? 1.Webservice:效率不高基于soap协议.项目中 ...

  7. 类与接口(五)java多态、方法重写、隐藏

    一.Java多态性 面向对象的三大特性:封装.继承.多态. 多态的类型,分为以下两种: 编译时多态: 指的是 方法重载.编译时多态是在编译时确定调用处选择那个重载方法,所以也叫 静态多态,算不上真正的 ...

  8. LeetCode题解之Unique Paths II

    1.题目描述 2.问题描述 使用动态规划算法,加上条件检测即可 3.代码 int uniquePathsWithObstacles(vector<vector<int>>&am ...

  9. python 流程控制(for循环语句)

    1,for循环基本语法 2, for循环常用序列 3,for循环 else使用方法 1,for循环基本语法 for iterating_var in sequence: statements(s) 2 ...

  10. .NET笔试题集(五)

    转载于:http://www.cnblogs.com/ForEvErNoME/archive/2012/09/15/2684938.html 1.什么是受管制的代码? 答:unsafe:非托管代码.不 ...