Springboot启动流程解析---new了一个SpringApplication

如下代码所示:

    //main方法A
    public static void main_A(String[] args) {
        SpringApplication springApplication = new SpringApplication(TestSpring.class);
        springApplication.run(args);
    }
    //main方法B
    public static void main_B(String[] args) {
        SpringApplication.run(TestSpring.class,args);
    }

对于这两个代码段,是我们进行springboot开发时常用的两种启动类写法,这里我们通过观察springboot的SpringApplication这个类的代码可以发现mainB方法其实调用的是这些代码

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);//这个run调用的是下面的run
	}
 	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}   

也就是说mainB这种写法其实是完全等价于mainA这种写法的。而对于mainA这种方法去调用springApplication对象的成员方法run方法中的这个run方法,我们一会进行解读,先观察SpringApplication这个类是如何创建对象的(之所以要研究构造方法,是因为mainB这种写法其实底层还是创建了一个springApplication对象,所以我们要研究这个对象是如何创建出来的)

1	public SpringApplication(Class<?>... primarySources) {
2		this(null, primarySources);
3	}
4	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
5		this.resourceLoader = resourceLoader;
6		Assert.notNull(primarySources, "PrimarySources must not be null");
7		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
8		this.webApplicationType = WebApplicationType.deduceFromClasspath();
9		this.bootstrapRegistryInitializers = new ArrayList<>(
10				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
11		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
12		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
13		this.mainApplicationClass = deduceMainApplicationClass();
14	}

无论是mainA还是mainB,最后调用SpringApplication的构造方法都是调用的上面代码的第一行这个构造方法,而第一行这个构造方法又调用了第4行这个构造方法,所以我们研究一下这个第四行的构造方法干了什么事情。

5		this.resourceLoader = resourceLoader;
6		Assert.notNull(primarySources, "PrimarySources must not be null");
7		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
8		this.webApplicationType = WebApplicationType.deduceFromClasspath();
9		this.bootstrapRegistryInitializers = new ArrayList<>(
10				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
11		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
12		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
13		this.mainApplicationClass = deduceMainApplicationClass();

我单独把上面5-13行的代码复制出来,方便我们分析,首先第5行是对springApplication的一个资源加载器进行赋值,但是我们无论是通过mainA还是mainB去启动一个springboot项目,这个资源加载器我们都是没有赋值的,也就是resourceLoader属性应该是null。第6行是一个断言,为了第7行赋值,第7行通过分析,可以知道这个位置其实就是赋值一下我们自己写的主启动类(分析不明白的,请自行研究源码,一步一步跟踪,很简单的),也就是TestSpring这个类。第8行,等号右边的这个WebApplicationType.deduceFromClasspath()这个方法,其实是一个枚举内部的方法,WebApplicationType就是一个枚举类,而这个第8行的作用就是判断当前这个springboot项目是否是一个web项目,而判断的方式很简单就是看我们的项目是否又spring-web的相关依赖(这也解释了为什么,小伙伴们自己去写springboot项目,pom文件里只用springboot依赖不用springweb依赖,就会发现项目启动了之后就立刻停止。这个属性会决定是否开启spring内置的tomcat),第9行第10行第11行和第12行这四行我们一起来观察。其实他们都调用了一个叫做getSpringFactoriesInstances的方法,这个方法做的事情就是去检查META-INF/spring.factories这个文件里面的自动装配信息,并形成一层缓存,这么说,小伙伴们可能不太清楚啥意思,我给大家贴一下代码

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	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;
	}

我们关注一下第二个getSpringFactoriesInstances方法,为什么是第二个呢,因为第一个最后调用的也是第二个,QAQ。第二个方法首先第一步搞一个classLoader,而我们刚才研究构造方法的时候,就有一个对resourceLoader赋值的操作,虽然我们赋值的是null,那这里面有什么联系吗?

	public ClassLoader getClassLoader() {
		if (this.resourceLoader != null) {
			return this.resourceLoader.getClassLoader();
		}
		return ClassUtils.getDefaultClassLoader();
	}

可以发现,classLoader本来打算从resourceLoader中获取,如果resourceLoader中没有的话,去调用ClassUtils.getDefaultClassLoader(),至于这个方法,我们不过多展开去看,反正getClassLoader这个方法就是返回了一个classLoader对象。然后我们继续关注之前的getSpringFactoriesInstance方法(这里就不再粘贴代码了,因为代码前面粘贴完了),首先搞了一个classLoader,紧接着又搞了一个names的一个set集合,里面的数据是通过调用SpringFactoriesLoader.loadFactoryNames()这个方法获取到的,而且将type参数和刚获取的classLoader传入进去,我们明确一下getSpringFactoriesInstance方法有一个参数是type这个参数是一个字节码对象,那也就是说SpringFactoriesLoader.loadFactoryNames()这个方法需要一个字节码对象和一个类加载器,其实根据我们已知的信息,充分发挥主观想象力就可以得知,这个方法就是通过给定的类加载器去加载指定的字节码对象,但是通过返回值是一个泛型为String的set集合我们可以知道,SpringFactoriesLoader.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());
	}

首先对classLoaderToUse进行赋值并判空,并且获取factoryType字节码参数的全限定类名,然后调用一个loadSpringFactories方法,该方法返回的是一个map,并要获取这个map里key值为字节码参数的全限定类名的值。那么我们继续研究一下loadSpringFactories这个方法

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			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();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			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;
	}

解读一下第一行从cache中获取classLoader对应的value值,如果获取到了就直接返回。所以前4行没啥好说的,相当于搞了一个缓存机制。我们继续往下看,在try-catch代码块中第一行就搞了一个classLoader.getResources(FACTORIES_RESOURCE_LOCATION)这个方法,而参数FACTORIES_RESOURCE_LOCATION的值实际上是"META-INF/spring.factories",那也就是说,要加载这个文件,然后下面的代码其实就是解析这个文件里面的内容,至于这个文件有什么作用,可以自行百度。

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	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这个方法其实就是加载各种的META-INF/spring.factories这个文件,并解析,而且还做了一层缓存,也就是说getSpringFactoriesInstances这个方法无论被调用多少次,底层找文件,解析,这个过程只会触发一次。然后我们继续看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;
	}

这个方法其实就是通过反射去实例化对象,然后返回一个list。
我们再看一下这行代码AnnotationAwareOrderComparator.sort(instances);

public class AnnotationAwareOrderComparator extends OrderComparator{
	public static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
	public static void sort(List<?> list) {
		if (list.size() > 1) {
			list.sort(INSTANCE);
		}
	}
}

这行api调用AnnotationAwareOrderComparator 这个类的compare方法(为什么是这个方法,java基础告诉我们List的sort方法需要传进来一个比较器,并且在底层调用比较器的compare方法),而这个类的父类OrderComparator实现了比较器这个接口

public class OrderComparator implements Comparator<Object> {
	public static final OrderComparator INSTANCE = new OrderComparator();
	@Override
	public int compare(@Nullable Object o1, @Nullable Object o2) {
		return doCompare(o1, o2, null);
	}
	private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
		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);
				}
			}
		}
		return (order != null ? order : getOrder(obj));
	}
	protected int getOrder(@Nullable Object obj) {
		if (obj != null) {
			Integer order = findOrder(obj);
			if (order != null) {
				return order;
			}
		}
		return Ordered.LOWEST_PRECEDENCE;
	}
	@Nullable
	protected Integer findOrder(Object obj) {
		return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
	}
}

有兴趣的同学可以去阅读一下这个类的代码,我把跟比较相关的代码都保留下来了,这个代码还是比较便于理解的,大概意思就是说,o1o2两个对象,首先看看他们谁实现了PriorityOrdered接口,谁实现这个接口并且另一个没实现,则实现接口的对象小,如果俩个对象都没实现这个接口,那么通过其他的比较情况继续去比较,这里只考虑实现compare这个方法,也就是说,如果没人实现PriorityOrdered这个方法,那么就看他俩有没有实现Ordered接口,实现这个接口的话,就调用getOrder方法,获取比较值,否则就返回一个整型最大值。
至此我们解释完了AnnotationAwareOrderComparator.sort(instances);这行代码之后,SpringApplication的getSpringFactoriesInstances这个方法也就介绍完毕。
回到构造方法那里

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
1		this.resourceLoader = resourceLoader;
2		Assert.notNull(primarySources, "PrimarySources must not be null");
3		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
4		this.webApplicationType = WebApplicationType.deduceFromClasspath();
5		this.bootstrapRegistryInitializers = new ArrayList<>(
6				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
7		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
8		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
9		this.mainApplicationClass = deduceMainApplicationClass();
	}

5,6,7,8这四个代码都调用了getSpringFactoriesInstances这个方法,而这个方法就是说,从所有META-INF\spring.factories文件中获取指定字节码对象的内容,并初始化返回。
第9行是给住启动类进行赋值,不过多深入讲解.至此如何new出来一个SpringApplication对象就介绍完毕。

总结一下哈,如果new出来一个SpringApplication对象,首先对SpringAppplication这个对象的一些属性赋值,并判断是否是一个web应用,并且初始化BootstrapRegistryInitilizer、ApplicationContextInitializer、ApplicationListener这三个接口的实现类。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值