SpringBoot打包机制
先看一眼spring-boot的maven插件打包后的target目录:
其中有一个.jar.original的文件,一个.jar文件,其中.jar.original才是原始的jar包,而.jar文件是经过spring-boot的maven插件处理过后的jar,springboot的maven插件会将原始jar重命名成.jar.original,然后按springboot自己的规范打出一个可执行的jar包。
将该jar包重命名成.zip文件后打开即可看到文件中的内容:
可以看到,springboot执行的jar并不是一个java标准的jar,其中包含了springboot自身定义的内容。我们再打开其中的META-INF/MANIFEST.MF文件,看看其中指定的内容:
Main-Class是其中的启动类,springboot打包出来的jar,启动类并不是工程中包含main方法的启动类,而是springboot自己的JarLauncher类,而工程中定义的启动类在这里变成了Start-Class,由此也可以看出springboot应用在IDE里通过main方法运行与通过java -jar命令运行的区别。
SpringBoot应用的启动
打开springboot的Main-Class JarLauncher类,其中包含main方法:
这是springboot启动的入口,JarLauncher类继承自ExecutableArchiveLauncher类,进入到launch方法可以看到springboot有一个archive的概念,archive是归档的意思,springboot打出来的jar包就是一个archive。
ExecutableArchiveLauncher类的createArchive方法可以看到启动时的archive创建,通过当前类找到jar包的路径,并创建JarFileArchive:
通过getNestedArchives方法可以看到Archive是一个递归的概念,JarFileArchive中可以有其它嵌套的Archive:
在launch方法中,有getClassPathArchives方法的调用,此方法中调用了前面的getNestedArchives方法,传入的Filter是lambda表达式,通过isNestedArchive方法对JarFileArchive中的Entry做过滤:
可以得知,springboot打出的archive jar包中,ROOT-INF/classes/目录被认为是一个嵌套的Archive,ROOT-INF/lib/下的每一个jar包也被认为是一个Archive
因为springboot的archive不是一个标准的jar包,java提供的ClassLoader无法加载到archive中的依赖以及class,springboot提供了新的classloader的实现用来做Archive中类的加载:
可以看到springboot使用的是LaunchedURLClassLoader这个ClassLoader做的类的加载。创建好类加载器后,需要调用Start-Class,即应用中的main方法:
MainMethodRunner.run方法非常简单,逻辑是通过反射调用应用的main:
这一步之后,进入到了应用代码中。
SpringBoot应用初始化
在使用spring boot时,我们在main方法中调用SpringApplication.run进行初始化:
在SpringApplication.run方法中做了Spring的初始化:
可以清晰的看到spring的初始化。创建ApplicationContext对象使用的是createApplicationContext方法,此方法实现如下:
对于非WEB应用,使用的ApplicationContext的实例是DEFAULT_CONTEXT_CLASS,此常用定义为AnnotationConfigApplicationContext类,在依赖注入中提到的这个类,此类用于实现注解配置的ApplicationContext。
回到启动类,启动类上加了一个@SpringBootApplication注解,此注解的定义:
前面Annotation的解析机制中提到,spring能通过解析@ComponentScan注解注册bean,但是springboot中,@ComponentScan标识在SpringBootApplication注解上,AnnotationConfigApplicationContext是定义在spring-context包中的,而@SpringBootApplication是定义在spring-boot-autoconfig中的注解,spring-context包并不依赖于spring-boot-autoconfig包,AnnotationConfigApplicationContext能通过@SpringBootApplication注解完成初始化是因为spring的注解处理工具类能识别出@SpringBootApplication的元注解@ComponentScan以及@AliasFor注解标识 的属性,实现逻辑在spring-core包中的AnnotationConfigUtils类中。
最后看看SpringApplication类的构造器:
在getSpringFactoriesInstances方法中,使用了SpringFactoriesLoader:
打开spring-boot相关的包,能看到spring.factories文件:
SpringFactoriesLoader是spring-core中提供的类,用于处理spring.factories文件,SpringFactoriesLoader.loadFactoryNames方法会读取类路径下的所有META-INF/spring.factories文件:
AutoConfiguration
回到@SpringBootApplication注解的定义:
其注解上被标记了@EnableAutoConfiguration注解,此注解用于实现autoconfiguration。打开此注解可以看到它实际上是使用了注解解析机制中的@Import注解:
如何使用ImportSelector就回到了@Import的处理逻辑中(见前面的文章)。可以看到,实现autoconfiguration的入口正是这个EnableAutoConfigurationImportSelector类,此类的selectImports方法返回的是需要作为配置类的类,这里的具体作用就是拿到类路径下的所有的自动配置的类,其代码实现:
AutoConfigurationMetadataLoader.loadMetadata方法的实现比较简单
即从META-INF/spring-autoconfigure-metadata.properties文件中加载数据,此文件在classpath下可以有多个(每个jar包中都可以有一个),随便打开一个配置文件,看看其中的内容:
里面都是一些配置类。@Import将这些配置类导入给spring的注解处理器ConfigurationClassParser就完成了bean的配置。
另外,在@SpringBootApplication注解上,还标记了一个@ComponentScan注解,其中的excludeFilters中有一个是AutoConfigurationExcludeFilter:
此类会判断如果class标了@Configuration并且此类包含spring.factories文件中,则在处理@Configuration注解时排除此类:
上面的代码中,getAutoConfigurations方法是从SpringFactoriesLoader中取的EnableAutoConfiguration关联的类。