学习随记
使用SpringBoot有几年了,但是一直没有机会了解底层流程,最近想把自己的学习过程记录下来,就借这个机会好好的读一下SpringBoot源码吧。
最近两天一有时间就看了下SpringBoot源码,本来打算下载Spring源码的,用gradle编译好像不太好使,再说Spring的话需要用一些配置,目前公司用配置这块用得比较少了,基本都是注解。(可能我没有接触过一些二次开发spring的公司)
所以不下源码也不影响整体的流程。(自我安慰)
言归正传,这篇文章是个开头,可能废话比较多,主要想把看源码的思路理清楚;
首先先建立好一个SpringBoot项目,然后创建一个启动类即可。
如下:
@SpringBootApplication
public class SpringBootDataApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootDataApp.class, args);
}
}
代码及其简单,最主要的方法当然是run方法了(废话,只有一个);
那就进入run方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
还有:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
这个就清楚一点了,一共两个点:
- new SpringApplication(primarySources);
- run(args);
new SpringApplication(primarySources);
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
一般第一个参数为null, 第二个参数就是你第一步传入的对象,就是当前的执行类。
从整体来看,这个方法执行的主要逻辑就是初始化,初始化集合,运行的类型,监听器等。
主要的方法在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()
- createSpringFactoriesInstances
先进loadFactoryNames();
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
主要的逻辑集中在loadSpringFactories();
这个方法做了什么事情呢?简单扫一下
第一步: 从缓存中获取值,直接返回,这说明这个方法肯定有很多地方调用,这也保证了后面通过加载器可以直接获取到对象的缓存。缓存为两个Sting 不难猜测第二个就是对应的类的全类名。(不然能存啥呢?)
第二步:通过classLoader.getResources(path)来获取对应路径的文件,并且返回一组Enumeration对象,其实这里就可以猜测到,肯定是读取"META-INF/spring.factories"的数据后,遍历URL;这个for循环没有break就表示这个直接做的读取操作。最后存在cache中,返回一个String的集合。
最后通过getOrDefault(factoryTypeName, Collections.emptyList())来过滤。
也就是ApplicationContextInitializer.class相关的类
第二个分支: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;
}
这个类相较于第一个类的话看起来就更简单,通过类的全类名,反射来生成对象,最终返回出去;
所以getSpringFactoriesInstances()这个类就是扫描"META-INF/spring.factories"这个文件生成全类名,通过反射生成实例对象。当然记住扫描肯定不是只扫一个spring.factories文件,是扫描整个项目的spring.factories文件。
当然 后面还有deduceMainApplicationClass()方法:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
这个方法看起来就很简单了,是不是有点似曾相识的感觉,没错,就是Debug时候的调用栈信息。
通过这个调用栈信息找到执行main的类,返回出去,记录下来,后面有大用。
好了,到这里的话,new SpringApplication()这个就执行完成了。
最后总结一下:
new SpringApplication() 主要是初始话一些集合,并且存放ApplicationContextInitializer.class 和 ApplicationListener.class 的相关类。这个相关类记录在项目下使用的resource/META-INF/spring.factories文件夹下面,并且实例化这些类(调用了newInstance()方法), 最后,把执行main的类记录下来,通过调用栈记录的。
这个地方我想一下,如果其他类执行了main方法调用了被@SpringApplication注解的类,那么,自动扫描时候以哪个包为主。会不会造成扫描的类遗漏?
有兴趣的小伙伴可以去试下。
好了,springBoot第一篇先写到这里吧。
第一次写博客,有些地方可能写错了,欢迎指正。