最近计划整理下SpringBoot的启动过程以及API请求返回过程,以便支持后续的部分内容,也方便实现一周一文的计划。
先做SpringBoot启动流程分析。
在SpringBoot启动代码的开头部分可以看到如下的内容:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
比较显眼的是 getSpringFactoriesInstances 方法——这个方法出现了两次,而且以后还会继续出现。
从代码上看来,这个方法的作用应该就是获取指定类型的实例集合。
具体如何,还得继续走走看。从这里一步步走下去,可以看到一个重载的方法,这个方法的内容比较实在:
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结构,接口->实现类集合),具体流程如下:
简单做些解释:
spring.factories文件本质上是一个.properties文件,也就是说,它的内容是键值对集合,下面是一段示例:
# 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 方法做的事情。这个方法不长,可以粘出来看看:
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 是不是指定接口的子类;还有就是在 BeanUtils . instantiateClass 中创建实例的时候修改了构造器的可见性范围,并提供了对Kotlin的支持。
这块儿内容大体上就这样了。至于这里获取到了哪些 ApplicationContextInitializer 和 ApplicationListener 的实例,以及它们的作用,在后续的部分会陆续提到。
End!