likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 55

前言

本文是作者写关于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));
   // 这里是将从spring.factories中读取到的配置类进行实例化
   // 参数分别是类型,参数类型,类加载器,参数,需要实例化的类的名称
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   // 这里的排序其实是用的List的.sort方法进行排序的
   // 后续补充一下List的排序方法sort
   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

loadSpringFactories(classLoaderToUse)返回的是一个Map,key是类全名,value是读取到的配置文件中的spring.factories中的。

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

getOrDefault是Map的一个方法,即如果存在则返回,如果不存在则返回一个默认值,这里是默认返回空集合。

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

附录

sort

该代码片段是进行列表排序的操作。具体来说,list是一个列表对象,INSTANCE是一个比较器对象。通过调用sort方法并传入INSTANCE作为参数,将对list进行排序,排序规则由INSTANCE比较器决定。

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

  • 举例

一个比较器(Comparator)在Java中通常实现java.util.Comparator接口,其中T代表需要比较的元素类型。以下是一个简单的示例,展示如何为自定义类Person编写一个按年龄升序排序的比较器:

    import java.util.Comparator;

public class Person {
    String name;
    int age;

    // 构造函数、getter、setter等省略...

    public static Comparator<Person> AgeComparator = new Comparator<Person>() {
        @Override
        public int compare(Person p1, Person p2) {
            return Integer.compare(p1.getAge(), p2.getAge());
        }
    };
}

在这个例子中:

1、定义了一个名为Person的类,包含name和age两个属性。

2、创建了一个静态内部类AgeComparator,它实现了Comparator接口,表示这是一个用于比较Person对象的比较器。

3、实现了compare方法,这是Comparator接口的核心方法。当进行排序时,会调用此方法来确定两个对象的相对顺序。这里使用Integer.compare方法比较p1和p2的年龄,返回值为:

  • 负数:表示p1的年龄小于p2的年龄,p1应该排在p2之前。
  • 零:表示两者年龄相等,在排序结果中它们的相对位置可以任意。
  • 正数:表示p1的年龄大于p2的年龄,p1应该排在p2之后。

其实这里可以简单的理解成返回false的时候第一个进入比较方法的第一个参数排前面。返回整数的时候,进入比较方法的第二个排前面。

使用这个比较器对Person列表按年龄排序的代码如下:

List<Person> peopleList = ...;  // 初始化一个Person列表
peopleList.sort(Person.AgeComparator);

如果你使用的是Java 8或更高版本,可以使用lambda表达式简化比较器的编写:

Comparator<Person> ageComparator = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()); 
peopleList.sort(ageComparator);
  • AnnotationAwareOrderComparator Spring的排序类实现AnnotationAwareOrderComparator extends OrderComparator

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

它的重写Comparator的比较方法compare是在父类OrderComparator中实现的。

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

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

这里的ProrityOrdered是Ordered的子类。

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

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
   // 判断是不是实现了ProrityOrdered接口
   boolean p1 = (o1 instanceof PriorityOrdered);
   boolean p2 = (o2 instanceof PriorityOrdered);
   // 第一个实现了、第二个没有实现,第一个排在前面
   if (p1 && !p2) {
      return -1;
   }
   // 第二个实现了、第一个没实现,第二个排在前面
   else if (p2 && !p1) {
      return 1;
   }

   // 获取用于排序的数字,其实我们经常定义的排序等级的数字,接着往下看
   int i1 = getOrder(o1, sourceProvider);
   int i2 = getOrder(o2, sourceProvider);
   return Integer.compare(i1, i2);
}
private int getOrder(@Nullable Object obj, @Nullable OrderSourceProvider sourceProvider) {
   Integer order = null;
   // 对象不为空 用于排序的对象也不为空
   if (obj != null && sourceProvider != null) {
      // 通过排序类获取 这里也可以看出我们其实也还可以自定义排序的方式
      Object orderSource = sourceProvider.getOrderSource(obj);
      if (orderSource != null) {
         if (orderSource.getClass().isArray()) {
            for (Object source : ObjectUtils.toObjectArray(orderSource)) {
               order = findOrder(source);
               if (order != null) {
                  break;
               }
            }
         }
         else {
            order = findOrder(orderSource);
         }
      }
   }
   // 这次我们主要看这个 因为上面传进来sourceProvider就是null
   return (order != null ? order : getOrder(obj));
}
  • getOrder
protected int getOrder(@Nullable Object obj) {
   if (obj != null) {
    // 主要是看这个方法
      Integer order = findOrder(obj);
      // 如果没有实现Ordered类就会返回null
      if (order != null) {
         return order;
      }
   }
   // 如果为空直接返回一个最大的值,感觉是作者真实没办法了,从上往下跟进来都不会有null进来了呢,下面就打脸了,这句话保留,警示自己,保持对大神的敬畏
   // 直接排最前面了
   return Ordered.LOWEST_PRECEDENCE;
}
  • findOrder 如果是实现了Ordered接口的直接调用getOrder方法,否则返回null。这里就揭露了我们实现Ordered接口实现getOrder方法可以达到排序的效果。
protected Integer findOrder(Object obj) {
   return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}

OK 今天先到这里吧。

See you next time :)

转载自:https://juejin.cn/post/7356108146630688779
评论
请登录