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包。