前言
本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。
读取spring.factories
昨天看到启动流程中通过构造方法创建了SpringApplication的实例。里面有一个重要的方法值得我们细看,那就是getSpringFactoriesInstances(Class<T>)
方法。
这方法也是SpringApplication这个对象的实例方法。也有方法重载,框架中真的很常见重载的方法。
java
// type 需要获取的类
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
java
// type 需要获取的类
// parameterTypes 构造方法的参数类型
// 构造方法的参数
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器,这里面有一个我们可以直接使用的工具类ClassUtils。见下面的代码片段一
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 重要方法是SpringFactoriesLoader.loadFactoryNames方法。该方法就是读取配置文件spring.factories。见下面对该方法的分析
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
代码片段一
java
public ClassLoader getClassLoader() {
// 这里的resourceLoader就是null,见构造方法的第一行,因为传入的就是null
if (this.resourceLoader != null) {
return this.resourceLoader.getClassLoader();
}
return ClassUtils.getDefaultClassLoader();
}
loadFactoryNames
java
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 类加载器
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 需要加载的类的名称
String factoryTypeName = factoryType.getName();
// 见下面对这两个方法的分析
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories
java
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从缓存中获取第一次自然是获取不到,这也是框架常用的写法。他们不可能连数据库或者是使用Redis这种第三个缓存,框架嘛存在更多的是无状态的数据,就在内存中用简单的Map缓冲一下就可以了,记得学习哟。
Map<String, List<String>> result = cache.get(classLoader);
// 这是减少if else的一种写法哟,study...
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 通过类加载器读取类路径下的资源文件。
// 看见了吗 baby这里就找到了为什么是META-INF/spring.factories了吧
// 常量FACTORIES_RESOURCE_LOCATION = "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。发现文件后缀为.factories的也能读成properties呢
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 将spring.factories文件中的key作为map的key,spring.factories中的value作为map的value进行返回。下图有spring.factories的内容截图,一看便知。
String factoryTypeName = ((String) entry.getKey()).trim();
// 这里有个工具类值得我们学习哟。就算是写业务代码也经常是要将逗号拼接的字符串转换成数组嘛。直接用Spring的StringUtils工具咯
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
// 这个写法也值得学习哟。是不是很少用到map的这个方法呀
// 这个方法作者用一个业务场景来说明: 编程中经常遇到这种数据结构,判断一个map中是否存在这个key,如果存在则处理value的数据,如果不存在,则创建一个满足value要求的数据结构放到value中。这个业务场景是不是要写很大一堆代码呀。map本身的这个computIfAbsent方法直接搞定。看源码还是有好处的吧
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
// 上面的注释翻译过来一目了然
// 将所有列表替换为包含唯一元素的不可修改列表
// 这又是一个优秀的写法呀,怎么办好像有什么业务场景让自己也过一下手瘾呀 呀呀呀
// 这里用了函数式接口BiFunction还有流式写法
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;
}
getOrDefault
这是一个排序的写法,这发现有很多实现,明天跑项目debug看着分析吧。
OK 今天先到这里吧。
See you next time :)