spring boot 启动记录

SpringBoot是Spring主推的基于"习惯优于配置"的原则,快速搭建应用的框架,它实现了jar in jar的加载方式。

spring boot应用打包之后,生成一个fat jar,里面包含了应用依赖的jar包,还有Spring boot loader相关的类

其中META-INF/MANIFEST.MF文件下的两个Class:
Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。
Start-Class是应用自己的Main函数。

程序启动时JarLauncher先找到自己所在的jar,然后创建了一个Archive(在spring boot里,一个archive可以是一个jar:JarFileArchive,也可以是一个文件目录:ExplodedArchive)。再获取到<工程>.jar/lib下面的所有jar文件。<工程>.jar既fat jar。
获取到所有Archive的URL之后,会构造一个自定义的ClassLoader:LaunchedURLClassLoader。
它从MANIFEST.MF里读取到Start-Class,创建一个新的线程来启动应用的Main函数。


工程以fat jar运行时,应用的main函数的ClassLoader是LaunchedURLClassLoader,它的parent是SystemClassLoader。
并且LaunchedURLClassLoader的urls是 fat jar里的BOOT-INF/classes!/目录和BOOT-INF/lib里的所有jar。
SystemClassLoader的urls是fat jar本身。


工程在IDE里启动,因为依赖的Jar都让IDE放到classpath里了,所以Spring boot直接启动了。
Spring的ClassLoader直接是SystemClassLoader。ClassLoader的urls包含全部的jar和自己的target/classes


工程在一个开放目录下启动Spring boot启动。所谓的开放目录就是把fat jar解压,然后直接启动应用。
Spring boot会判断当前是否在一个目录里,如果是的,则构造一个ExplodedArchive(前面在jar里时是JarFileArchive),后面的启动流程类似fat jar的。

执行应用的main函数的ClassLoader是LaunchedURLClassLoader,它的parent是SystemClassLoader。
LaunchedURLClassLoader的urls是解压目录里的BOOT-INF/classes/和/BOOT-INF/lib/下面的jar包。
SystemClassLoader的urls只有当前目录
目录形式会有更好的兼容性。

Start-Class的代码里基本上就一句话
SpringApplication.run(Application.class,args);

这个main方法中,调用了SpringApplication的静态run方法,并将Application类对象和main方法的参数args作为参数传递了进去。

核心代码如下:

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
}

1、SpringApplicationRunListeners (初始化监听器)

2、配置参数和环境

3、打印Banner

4、创建ApplicationContext(),在里面会判断webApplicationType,然后具体Class.forName哪个ApplicationContext(AnnotationConfigApplicationContext、                       AnnotationConfigServletWebServerApplicationContext、                       AnnotationConfigReactiveWebServerApplicationContext)

5、使用扩展机制加载其他configure

6、准备context

7、刷新context
。。。。。。


Spring Boot里用于解耦的扩展机制:Spring Factories。这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的。
其主要功能就是从指定的配置文件(SpringBoot的autoconfigure依赖包)中的META-INF/spring.factories加载配置。

在Spring中也有一种类似与Java SPI的加载机制。它从spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这种自定义的SPI机制是Spring Boot Starter实现的基础。

Java SPI:
我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。
一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Java SPI约定:

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:com.xxx.interface=com.xxx.classname
如果一个接口希望配置多个实现类,可以使用','进行分割。

Factories机制可以让SDK或者Starter的使用只需要很少或者不需要进行配置,只需要在服务中引入我们的jar包。

猜你喜欢

转载自my.oschina.net/u/2357969/blog/1821253