最近计划整理下SpringBoot的启动过程以及API请求返回过程,以便支持后续的部分内容,也方便实现一周一文的计划。
先做SpringBoot启动流程分析。
初见
在SpringBoot启动代码的开头部分可以看到如下的内容:
1 2 |
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); |
比较显眼的是getSpringFactoriesInstances
方法——这个方法出现了两次,而且以后还会继续出现。
从代码上看来,这个方法的作用应该就是获取指定类型的实例集合。
具体如何,还得继续走走看。从这里一步步走下去,可以看到一个重载的方法,这个方法的内容比较实在:
1 2 3 4 5 6 7 8 |
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } |
关键是方法体中的三四两行。第三行应该是获取了名称集合,第四行则应是获取了名称对应的实例集合。
问名
首先看看SpringFactoriesLoader.loadFactoryNames
方法做了哪些事情。这个方法的核心内容在于它调用的loadSpringFactories
方法。loadSpringFactories
的方法体稍稍长了一些,沾出来显得太啰嗦,所以仅在下面介绍下这个方法具体做了那些事情。
这个类的作用是根据类加载器获取类名称集合(map结构,接口->实现类集合),具体流程如下:
- 首先尝试从内存缓存中获取,如获取到就立即返回,没有则继续下面内容;
- 从类加载路径(的jar文件)中获取全部的spring.factories文件路径;
- 循环遍历读取这些文件中的键值对(K->List[V]);
- 将读取内容放入内存缓存,下次再调用这个方法时会优先从缓存中获取;
- 返回读取到的全部键值对集合(Map[K,List[V]]结构)。
简单做些解释:
spring.factories文件本质上是一个.properties文件,也就是说,它的内容是键值对集合,下面是一段示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers |
这个片段中有三个键值对。根据后面的代码可知spring.factories文件中每个键值对的key是一个接口的名称,value则是实现类名称的集合(以“,”分隔)。
spring.factories文件中的内容会被读取到一个(K->List[V])结构的Map中。最终全部这些内容又会以类加载器实例为key保存在内存缓存中。
然后,SpringFactoriesLoader.loadFactoryNames
方法就可以根据接口的名称获取到接口的实现类的名称集合了。
得意
在得到类名称集合后就是根据类名构建类对应的实例了。这是createSpringFactoriesInstances
方法做的事情。这个方法不长,可以粘出来看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); }catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; } |
方法的内容很清晰也很简单:
- 根据方法名获取
Class
实例; - 通过
Class
实例获取构造器; - 使用构造器构建类实例。
很普通的一个反射过程。期间有一个校验是判断Class
是不是指定接口的子类;还有就是在BeanUtils.instantiateClass
中创建实例的时候修改了构造器的可见性范围,并提供了对Kotlin的支持。
这块儿内容大体上就这样了。至于这里获取到了哪些ApplicationContextInitializer
和ApplicationListener
的实例,以及它们的作用,在后续的部分会陆续提到。
End!
发表评论