Spring Boot源码分析二:启动流程
前言
本文是作者写关于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();
}
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;
}
getOrDefault
loadSpringFactories(classLoaderToUse)返回的是一个Map,key是类全名,value是读取到的配置文件中的spring.factories中的。
getOrDefault是Map的一个方法,即如果存在则返回,如果不存在则返回一个默认值,这里是默认返回空集合。
附录
sort
该代码片段是进行列表排序的操作。具体来说,list是一个列表对象,INSTANCE是一个比较器对象。通过调用sort方法并传入INSTANCE作为参数,将对list进行排序,排序规则由INSTANCE比较器决定。
- 举例
一个比较器(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
。
它的重写Comparator的比较方法compare是在父类OrderComparator中实现的。
这里的ProrityOrdered是Ordered的子类。
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