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

前言

本文是作者写关于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 :)

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
vipxieliang1 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
kybs19911 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端