SpringBoot源码阅读

作者: 无醉_1866 | 来源:发表于2019-10-04 21:34 被阅读0次

    打包机制

    先看一眼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命令运行的区别。

    Spring Boot应用的启动

    打开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:

    这一步之后,进入到了应用代码中。

    Spring Boot应用初始化

    在使用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关联的类。

    相关文章

      网友评论

        本文标题:SpringBoot源码阅读

        本文链接:https://www.haomeiwen.com/subject/kkwspctx.html