Spring Boot 扩展点应用之工厂加载机制
Spring工厂加载机制,即Spring Factories Loader,核心逻辑是使用SpringFactoriesLoader加载由用户实现的类,并配置在约定好的META-INF/spring.factories路径下,该机制可以为框架上下文动态的增加扩展。
该机制类似于Java SPI,给用户提供可扩展的钩子,从而达到对框架的自定义扩展功能。
核心实现类 SpringFactoriesLoader
SpringFactoriesLoader是Spring工厂加载机制的核心底层实现类。它的主要作用是 从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这个注解,这个注解会ImportAutoConfigurationImportSelector这个类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector 中有一个方法就是加载 EnableAutoConfiguration 为 key 的实现配置类。
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
this.beanClassLoader)
}
SpringFactoriesLoader loadFactories 加载 所有以 factoryClass 为 Key 的实现类
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;
}
调用 loadSpringFactories 从META-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.properties 中 context.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 扩展点应用之工厂加载机制的更多相关文章
- Spring Boot 启动(二) Environment 加载
Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...
- spring boot找不到或无法加载主类 io.renren.RenrenApplication
spring boot找不到或无法加载主类 io.renren.RenrenApplication 出现问题: spring boot 项目以前一直是好好的,用mvn clean package 打包 ...
- Spring IOC - 控制反转(依赖注入) - 懒加载机制
懒加载机制 Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少的时间问题不大,但一旦bean非常多时,Spring需要在启动的过程中 ...
- spring boot 使用不同的profile来加载不同的配置文件
在开发过程之中,经常需要在开发和测试环境中进行互相切换,当切换的同时需要加载相应的配置文件,因此要经常 性的对配置文件进行相应的修改,长此以往感到十分痛苦.如果能针对开发和测试环境分别建两个不同的配置 ...
- spring boot 学习入门篇【spring boot项目的搭建以及如何加载jsp界面】
[ 前言] Spring Boot 简介:Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置, ...
- Spring Boot 2.4.0正式发布,全新的配置文件加载机制(不向下兼容)
千里之行,始于足下.关注公众号[BAT的乌托邦],有Spring技术栈.MyBatis.JVM.中间件等小而美的原创专栏供以免费学习.分享.成长,拒绝浅尝辄止.本文已被 https://www.you ...
- 「新特性」Spring Boot 全局懒加载机制了解一下
关于延迟加载 在 Spring 中,默认情况下所有定的 bean 及其依赖项目都是在应用启动时创建容器上下文是被初始化的.测试代码如下: @Slf4j @Configuration public cl ...
- 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)
doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...
- Spring源码分析:非懒加载的单例Bean初始化过程(下)
上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...
随机推荐
- C#基础知识回顾---你不知道的Lazy<T>
对象的创建方式,始终代表了软件工业的生产力方向,代表了先进软件技术发展的方向,也代表了广大程序开发者的集体智慧.以new的方式创建,通过工厂方法,利用IoC容器,都以不同的方式实现了活生生实例成员的创 ...
- Razor 中的@rendersection
在使用布局页时,可以指定页面中某处的渲染,具体的用@rendersection来做.如在布局页中要渲染一段自定义的脚本, @RenderSection("scripts", req ...
- Nginx初探
nginx是一款轻量级的web服务器.反向代理服务器和电子邮件服务器,占有内存少,并发能力强. 本文将简单介绍如何安装.启动nginx,部署web项目,应用反向代理. 一.安装 可参考https:// ...
- Python生成语音
from aip import AipSpeech # 利用百度语音合成音频文件 """ 你的 APPID AK SK """ APP_ID ...
- Incircle and Circumcircle(二分+几何)浙大月赛zoj3806(详解版)图
Incircle and Circumcircle Time Limit: 2 Seconds Memory Limit: 65536 KB Special Judge A triangle is o ...
- EF Codefirst(二)数据注释
CodeFirst通过分析我们在代码里编写的类,以及类之间的关系生成数据库表,以及表之间的各种关系.数据库的表会涉及到主键,外键,列是否为空,列类型等等. 我们要通过怎样的方式来暴露这些信息呢? ...
- 排序算法(3)--Insert Sorting--插入排序[3]--Shell Sort--希尔排序
1.基本思想 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止. 2.实现原理 对于n ...
- webpack中mainifest.js vendor.js app.js 三者的区别
场景: 大家在利用构建工具进行应用最后的打包过程中,我们希望做到的是将业务代码和第三方引用模块代码分开打包. 因为第三方引用模块代码通常很大,而且在不引入新的模块之前基本上是不会变动的.所以我们需要将 ...
- 怎么配置wamp下mysql的编码
Windows下的Apache+Mysql/MariaDB+Perl/PHP/Python,一组常用来搭建动态网站或者服务器的开源软件,本身都是各自独立的程序,但是因为常被放在一起使用,拥有了越来越高 ...
- adb shell 操作sqlite数据库
程序中数据库文件路径在/data/data/<package name>/databases/ adb shell 进入adb命令行 ls 列出当前文件夹下所有文件 假设有一个名为a ...