【spring学习笔记 一】spring启动流程概览

导读:本篇文章讲解 【spring学习笔记 一】spring启动流程概览,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,原文地址:Java

也许你感觉自己的努力总是徒劳无功,但不必怀疑,你每天都离顶点更进一步。今天的你离顶点还遥遥无期。但你通过今天的努力,积蓄了明天勇攀高峰的力量。加油!

spring流程概览

这篇文章主要聊聊spring启动流程经历了哪些步骤,以及它们做了什么。

一般我们都是选择使用SpringApplication.run()这个静态方法来启动spring。

还是一样我们先大体上看下它做了什么

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

可以看出这里分成了两步,
第一步:创建了一个SpringApplication对象
第二步:访问其run方法
那我们就先来看看创建SpringApplication对象做了什么。

new SpringApplication()

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)); //初始化一些ApplicationContextInitializer类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//初始化一些ApplicationListener
		this.mainApplicationClass = deduceMainApplicationClass();//推测应用入口
	}

对于我们来说比较重要的就是setInitializers方法和setListeners方法,这两个位置使我们可以自定的一个切入口。
那么下面我们就来看看 他是如何寻找ApplicationContextInitializer和ApplicationListener的。

setInitializers() 和 setListeners()

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		//通过类名称 找到实现的限定名
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//通过实现的限定名实例化对象
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

还是分为两步,第一步找到传入的类名称(例如ApplicationContextInitializer)的所有实现的限定类名。 第二步通过全限定名实例化成对象

那么它是如何找到实现了type的限定类名呢?

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		...
		Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		...
		return result;
	}

通过加载FACTORIES_RESOURCE_LOCATION 这个路径下的文件
那么FACTORIES_RESOURCE_LOCATION 的值是多少呢?

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

文件就是这样,截取springboot的一部分配置信息

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

好了,到这里限定类名如何获取我们已经知道,接下来就是看如何实例化了。

	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);//通过限定类名 获取Class对象
				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对象,第二步获取构造方法,第三步调用构造方法实例对象。

到这里创建SpringApplication的一些工作我们已经了解完了,我们也知道如果添加自定义的ApplicationContextInitializer和ApplicationListener了。
下面我们就看看,启动spring容器又做了什么,以及ApplicationContextInitializer和ApplicationListener都分别在哪些时间节点触发,中途是否又有其他的扩展点提供给我们使用。

run()

由于这段代码比较长,我先贴一部分经过我删减的流程,主要是为了使我们更聚焦,这样我们更容易从宏观上去把控整个流程。后面一些文章会分析其中的细节。

public ConfigurableApplicationContext run(String... args) {
		...
		//创建一个事件广播器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//发布一个启动广播
		listeners.starting();
			...
			//准备环境,装载一些PropertySources
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//创建容器,由前面没聊到的webApplicationType 决定
			context = createApplicationContext();
			//很眼熟把,加载SpringBootExceptionReporter(这个主要是处理异常)
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//准备容器
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//刷新容器
			refreshContext(context);
			...
			//发布一个启动完毕的广播
			listeners.started(context);
			...
		return context;
	}

中间有些步骤已经被我省略了,因为那部分我们很难做出改变。 现在来看这个启动步骤就变得特别清晰了。

  1. 准备环境
  2. 创建容器
  3. 准备容器
  4. 刷新容器

搞清楚了它的主体流程,我们接下来就看看它们分别又做了什么。

prepareEnvironment()

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		//创建环境容器,总共分为三类, 一类非Web,一类Web容器,一类内嵌型web容器
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//环境属性的加载,默认的properties,profile
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//发送广播 ApplicationEnvironmentPreparedEvent 事件
		listeners.environmentPrepared(environment);
		//将前面的properties属性注入到this中
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertEnvironmentIfNecessary(environment,
							deduceEnvironmentClass());
		}
		//给我的感觉像是,在sources的头部放了一个整合了所有sources的整合包?
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

还是分步走,

  1. 第一步创建一个环境、
  2. 环境属性的加载,将启动参数和defaultProperties属性封装PropertySources对象,其中启动参数排在第一个使用的优先级最高,defaultProperties排在最后一个也就是使用的优先级最低。
  3. 广播enviromentPrepared事件
  4. 将现有的PropertySources对象中前缀为”spring.main”的属性注入当前springApplication中

就此环境容器已经准备完毕了

createApplicationContext()

接下来就是创建容器了,
这个比较简单就是根据上面webApplicationType找到对应的限定类名,然后实例化一个容器

prepareContext()

这里面涉及到一个重要的方法,前面我们提到过如何去加载一个ApplicationContextInitializer,而使用,就是在prepareContext里面

private void prepareContext(...) {
		...
		//访问ApplicationContextInitialized
		applyInitializers(context);
		//对事件ApplicationContextInitializedEvent进行广播
		listeners.contextPrepared(context);
		...
		//进行广播
		listeners.contextLoaded(context);
	}

applyInitializers()就是遍历访问spring加载进来的所有ApplicationContextInitializer的实现,并调用他们的initialize方法。

refreshContext ()

刷新容器,这个就是spring启动的重头戏,里面的流程非常多,而且有很多我们对于spring感性的认知,在里面也有了详细的描述,由于篇幅问题,我将会把它独立成一个单独的章节。
这里先笼统的说下它的功能,包括了 bean前置的一些处理,bean内部属性的处理,bean自身的处理,以及非常多的扩展的处理等等。

总结

spring本身是一个非常庞当的项目,可能上述有一些重要的流程被我忽略掉了,如果有错误的地方欢迎读者指正。

与君共勉。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/121873.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!