第五十六章 Spring之假如让你来写Boot——SPI篇
Spring源码阅读目录
第一部分——IOC篇
第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇
第二部分——AOP篇
第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇
第三部分——事务篇
第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇
第四部分——MVC篇
第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇
第五部分——Boot篇
第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇
文章目录
前言
对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了

所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
书接上回,在上篇 第五十五章 Spring之假如让你来写Boot——注解篇(下) 中,A君 折腾了好几天,终于把 组合注解 实现了。接下来看看 A君 会有什么骚操作吧
尝试动手写IOC容器
出场人物:A君(苦逼的开发)、老大(项目经理)
背景:老大 要求 A君在一周内开发个简单的 IOC容器
前情提要:A君 终于把 组合注解 实现了。。。
四十六版 SPI
“怎么样,注解 部分做下来的感觉如何?” 办公室内,老大 看着 A君
“快吐了,没想到 组合注解 这么麻烦!!!接下来不会也这么恶心吧?” A君 愤懑道
“嘿嘿,不会不会,接下来给你个简单的任务。” 老大 狡黠的笑道,“你之前也提到的 SPI,你在技术调研的时候,也明白了 JDK 自带的 SPI 机制存在的弊端:只能支持对应的接口实现,有些功能我们可以定义接口,而我们往往无法规定 自动装配 的接口,这部分内容需要改造成即支持接口了,又支持 注解的形式。”
“好吧。” A君 无奈答应道
SPI加载器
A君 回去后翻了翻资料,发现 老大 说的天花乱坠,实际上无非就是key不一样罢了。例如:
有统一接口:

自动装配:

既然明白了是怎么一回事,那么后边的是也就好办了。不过现在要做的事,就是把对应的配置文件加载出来,这里自然就是根据META-INF/路径下的spring.factories文件了。之后就是通过key进行过滤,找到对应的类即可。这个直接提供一个工具类即可。A君 新增SpringFactoriesLoader类,代码如下:
/**
* SPI加载器
*/
public final class SpringFactoriesLoader {
/**
* 指定配置文件路径
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() {
}
public static <T> List<T> loadFactories(Class<T> factoryType, ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
//AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
/**
* 获取指定类型的配置
*/
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
/**
* 加载配置文件
*
* @param classLoader
* @return
*/
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
/**
* 获取所有 META-INF/spring.factories 文件
*/
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
/**
* 加载所有配置
*/
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
/**
*去重
*/
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
}
新版SPI加载器
再定义SpringFactoriesLoader类进行加载之后,A君 开始寻思一个问题,能不能对其进行再一次的简化。瞅了半天,A君 发现一件事:基于接口的自是不用多说,本身接口都不一样,key也就不一样了,这个没有办法简化,但是基于注解的就不一样了,key都是org.springframework.boot.autoconfigure.EnableAutoConfiguration,那么这部分内容可以省略。秉承着 约定大于配置 的原则,A君 规定:META-INF/spring/文件夹下,所有后缀为imports的文件,即为基于注解的 SPI。有了思路,代码其实也就没什么东西了。A君 新增ImportCandidates类,代码如下:
/**
* 新版SPI加载器,加载基于注解的自动装配的文件
*/
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION = "META-INF/spring/%s.imports";
private static final String COMMENT_START = "#";
private final List<String> candidates;
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
ClassLoader classLoaderToUse = decideClassloader(classLoader);
//配置路径
String location = String.format(LOCATION, annotation.getName());
//获取类路径下所有符合条件的文件url
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> autoConfigurations = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
autoConfigurations.addAll(readAutoConfigurations(url));
}
return new ImportCandidates(autoConfigurations);
}
//省略其他代码。。。
}
日志系统
虽然定义说的高大上,实际上代码就那么回事。但是接下来测试部分却让 A君 头疼了,该以哪个作为例子呢?思来想去,A君 准备从日志开始入手。日志框架不可谓不多啊,但是主流的就几个:JUL、Logback、Log4J 。它们之间的爱恨情仇 A君 这里就不打算去了解了,只知道这几个日志是不同实现就行了。而日志属于可以定义接口的一类,于是,A君 定义LoggingSystemFactory接口,代码如下:
public interface LoggingSystemFactory {
static LoggingSystemFactory fromSpringFactories() {
return new DelegatingLoggingSystemFactory(
(classLoader) -> SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader));
}
LoggingSystem getLoggingSystem(ClassLoader classLoader);
}
其他部分就不展开细说了,这些东西不是整个框架的重点。接下来就需要配置spring.factories文件了。A君 新增spring.factories文件,配置如下:
# Logging Systems
com.hqd.ch03.v46.boot.logging.LoggingSystemFactory=\
com.hqd.ch03.v46.boot.logging.logback.LogbackLoggingSystem.Factory
测试
好了,现在所有东西都准备好了,可以进入测试阶段了。A君 编写测试代码如下:
@Test
public void v46() throws NoSuchMethodException {
System.out.println("############# 第四十六版: SPI篇 #############");
LoggingSystem loggingSystem = LoggingSystem.get(Thread.currentThread().getContextClassLoader());
System.out.println(loggingSystem);
}
测试结果如下:

OK,测试结果并没有出现意外。A君 或多或少松了口气,不过想到 老大 一贯的尿性,估计今天也就是抛砖引玉,明天看来又有场恶战了。不过呢,今天就到这里。。。

总结
正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

浙公网安备 33010602011771号