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.factoriesEnumeration<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 {@Overridepublic 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/249345.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue.js使用-http请求

Vue.js使用-ajax使用 1.为什么要使用ajax 前面的例子&#xff0c;使用的是本地模拟数据&#xff0c;通过ajax请求服务器数据。 2.使用jquery的ajax库示例 new Vue({el: #app,data: {searchQuery: ,columns: [{name: name, iskey: true}, {name: age},{name: sex, dataSource:…

跨域(Cross-Domain) AJAX for IE8 and IE9

1、有过这样一段代码&#xff0c;是ajax $.ajax({url: "http://127.0.0.1:9001",type: "POST",data: JSON.stringify({"reqMsg":"12345"}),dataType: json,timeout: 1000 * 30,success: function (response) {if(response.n6){dosomet…

移动WEB的页面布局

随着移动互联网的日益普遍&#xff0c;现在移动版本的web应用也应用而生&#xff0c;那么在做移动web页面布局的过程中&#xff0c;应该注意哪些要点呢&#xff1f;现把个人的一些学习经验总结如下&#xff1a; 要点一、piexl 1px 2dp dp dpr dpi ppi 要点二、viewport io…

AnswerOpenCV(1001-1007)一周佳作欣赏

外国不过十一&#xff0c;所以利用十一假期&#xff0c;看看他们都在干什么。一、小白问题http://answers.opencv.org/question/199987/contour-single-blob-with-multiple-object/ Contour Single blob with multiple objectHi to everyone. Im developing an object shape id…

Mysql 开启远程连接

在日常的数据库的使用过程&#xff0c;往往会因为连接权限的问题搞得我们焦头烂额&#xff0c;今天我把我们在数据库连接上的几个误区简单做个记录。内容如下&#xff1a; 误区一&#xff1a;MYSQL密码和数据库密码的区别 mysql密码是我们在安装mysql服务是设置的密码&#xf…

基于jsp+servlet完成的用户注册

思考 &#xff1a; 需要创建实体类吗? 需要创建表吗 |----User 存在、不需要创建了&#xff01;表同理、也不需要了 1.设计dao接口 package cn.javabs.usermanager.dao;import cn.javabs.usermanager.entity.User;/*** 用户的dao接口的设计* author Mryang**/ public interfa…

vue resource then

https://www.cnblogs.com/chenhuichao/p/8308993.html

云开发创建云函数

安装wx-server-sdk时候&#xff0c;终端报错如下&#xff1a; 解决方法&#xff1a; 运行&#xff1a;npm cache clean --force即可 转载于:https://www.cnblogs.com/moguzi12345/p/9758842.html

Java8新特性——函数式接口

目录 一、介绍 二、示例 &#xff08;一&#xff09;Consumer 源码解析 测试示例 &#xff08;二&#xff09;Comparator &#xff08;三&#xff09;Predicate 三、应用 四、总结 一、介绍 FunctionalInterface是一种信息注解类型&#xff0c;用于指明接口类型声明…

CSS3笔记之基础篇(一)边框

效果一、圆角效果 border-radius 实心上半圆&#xff1a; 方法&#xff1a;把高度(height)设为宽度&#xff08;width&#xff09;的一半&#xff0c;并且只设置左上角和右上角的半径与元素的高度一致&#xff08;大于也是可以的&#xff09;。 div {height:50px;/*是width…

JavaSE之Java基础(1)

1、为什么重写equals还要重写hashcode 首先equals与hashcode间的关系是这样的&#xff1a; 1、如果两个对象相同&#xff08;即用equals比较返回true&#xff09;&#xff0c;那么它们的hashCode值一定要相同&#xff1b; 2、如果两个对象的hashCode相同&#xff0c;它们并不一…

bootstarp table

https://www.cnblogs.com/laowangc/p/8875526.html

高级组件——弹出式菜单JPopupMenu

弹出式菜单JPopupMenu&#xff0c;需要用到鼠标事件。MouseListener必须要实现所有接口&#xff0c;MouseAdapter是类&#xff0c;只写你关心的方法&#xff0c;即MouseAdapter实现了MouseListener中的方法 import javax.swing.*; import java.awt.*; import java.awt.event.Mo…

CSS3笔记之基础篇(二)颜色和渐变色彩

效果一、颜色之RGBA RGB是一种色彩标准&#xff0c;是由红(R)、绿(G)、蓝(B)的变化以及相互叠加来得到各式各样的颜色。RGBA是在RGB的基础上增加了控制alpha透明度的参数。 语法&#xff1a; color&#xff1a;rgba(R,G,B,A) 以上R、G、B三个参数&#xff0c;正整数值的取值…

19_03_26校内训练[魔法卡片]

题意 有n张有序的卡片&#xff0c;每张卡片上恰有[1,m]中的每一个数&#xff0c;数字写在正面或反面。每次询问区间[l,r]&#xff0c;你可以将卡片上下颠倒&#xff0c;问区间中数字在卡片上方的并的平方和最大是多少。q,n*m≤1,000,000。 思考 一个很重要的性质&#xff0c;若…

vue 静态图片引入

https://blog.csdn.net/weixin_33862188/article/details/93325502

c:if test=/c:if 使用

1、页面引用<%taglib uri"http://java.sun.com/jsp/jstl/core" prefix"c"%> 2、整形判断&#xff1a; <c:if test"${TEST 1}"> </c:if> 3、判断非空&#xff1a; <c:if test"${empty TEST}"> TEST为空 <…

CSS3笔记之基础篇(三)文字与字体

要点一、text-overflow与word-wrap text-overflow&#xff1a;设置是否使用一个省略标记&#xff08;...&#xff09;标示对象内文本的溢出。 word-wrap&#xff1a;设置文本行为&#xff0c;当前行超过指定容器的边界时是否断开转行。 语法如下&#xff1a; 注意&#xff1…

XV6操作系统代码阅读心得(二):进程

1. 进程的基本概念 从抽象的意义来说&#xff0c;进程是指一个正在运行的程序的实例&#xff0c;而线程是一个CPU指令执行流的最小单位。进程是操作系统资源分配的最小单位&#xff0c;线程是操作系统中调度的最小单位。从实现的角度上讲&#xff0c;XV6系统中只实现了进程&…

webservices

https://blog.csdn.net/VitaminZH/article/details/81123571