前言

本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。

读取spring.factories

昨天看到启动流程中通过构造方法创建了SpringApplication的实例。里面有一个重要的方法值得我们细看,那就是getSpringFactoriesInstances(Class<T>)方法。

这方法也是SpringApplication这个对象的实例方法。也有方法重载,框架中真的很常见重载的方法。

// type 需要获取的类
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 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;
}

代码片段一

public ClassLoader getClassLoader() {
   // 这里的resourceLoader就是null,见构造方法的第一行,因为传入的就是null
   if (this.resourceLoader != null) {
      return this.resourceLoader.getClassLoader();
   }
   return ClassUtils.getDefaultClassLoader();
}

Spring Boot源码分析二:启动流程

loadFactoryNames

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

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;
}

Spring Boot源码分析二:启动流程

getOrDefault

这是一个排序的写法,这发现有很多实现,明天跑项目debug看着分析吧。

OK 今天先到这里吧。

See you next time :)