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. JSON & Ajax

    Ajax是异步JavaScript和XML是用来在客户端作为一组相互关联的Web开发技术,以创建异步Web应用程序. Ajax模型,Web应用程序可以发送数据和检索数据从一个服务器,而不干扰现有的页面 ...

  2. [javaSE] 看博客学习java并发编程

    共享性 多线程操作同一个数据,产生线程安全问题 新建一个类ShareData 设计一个int 型的成员变量count 设计一个成员方法addCount(),把count变量++ 在main函数中开启多 ...

  3. 全局唯一订单号生成方法(参考snowflake)

    backgroud Snowflake is a network service for generating unique ID numbers at high scale with some si ...

  4. Java 支付宝支付,退款,单笔转账到支付宝账户(单笔转账到支付宝账户)

    上次分享了支付宝订单退款的代码,今天分享一下支付宝转账的操作.  现在是有一个余额提现的功能,本来是打算做提现到银行卡的,但是客户嫌麻烦不想注册银联的开放平台账户,就说先提现到支付宝就行,二期再做银行 ...

  5. Charlie's Change(完全背包+路径记忆)

    Charlie's Change Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 3176   Accepted: 913 D ...

  6. Oracle面试的基本题

    事务 事务的概念 事务就是对数据操作的一系列指令集合. 事务的四个特性 原子性 事务的操作要么全部成功,要么全部失败,如果有一个指令失败,那么事务回滚到初始状态. 一致性 事务的执行不能破坏数据的完整 ...

  7. cf896C. Willem, Chtholly and Seniorious(ODT)

    题意 题目链接 Sol ODT板子题.就是用set维护连续段的大暴力.. 然鹅我抄的板子本题RE提交AC??.. 具体来说,用50 50 658073485 946088556这个数据测试下面的代码, ...

  8. JS中判断数据类型的几种方法

    1⃣️首先我们来了解一下js中的数据类型 1.基本数据类型:Undefined.Null.Boolean.Number.String(值类型) 2.复杂数据类型:Object(引用类型) (值类型和引 ...

  9. JAVA后端笔试试题(一)

    2017年6月7日,天气晴转阴.心情还不错. 上周六参加了自己的第一场笔试,感觉很糟糕,主要是对基础知识掌握不扎实,现在把笔试中的部分问题总结归纳如下,便于以后查看. 1.GC是什么?为什么要GC? ...

  10. 高性能JavaScript(算法和流程控制)

    在大多与编程语言中,代码的执行时间大部分消耗在循环中,是提升性能必须关注的要点之一 循环的类型 for循环(它由四部分组成:初始化.前测条件.后执行体.循环体.) for(var i = 0; i & ...