一文带你Spring Boot原理分析
传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件等;相较而言,SpringBoot框架显得更加方便、快捷和高效。那么Spring Boot框架究竟如何做到这些呢?下面分别针对SpringBoot框架的依赖管理、自动配置和执行流程进行深入分析和介绍。
1.Spring Boot依赖管理
1.1 spring-boot-starter-parent依赖
我们在创建的Spring Boot项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下。
其中spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理。使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码如下。
继续查看spring-boot-dependencies底层源文件,核心代码如下。
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如mysql、spring、tomcat等,都有与Spring Boot 2.7.5版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。需要说明的是,如果pom.xml引入的依赖文件不是spring- boot-starter-parent管理的,那么在pom.xml引入依赖文件时,需要使用version标签指定依赖文件的版本号。
1.2 spring-boot-starter-web依赖
spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来,又是怎样管理的呢?下面查看项目pom.xml文件中的spring-boot-starter-web依赖文件源码,核心代码如下。
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖文件,它对Web开发场景所需的依赖文件进行了统一管理。
正是如此,在pom.xm中引入sring-bo-tartr -web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring- boot-starter-parent父依赖进行统一管理。
2.Spring Boot自动配置
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication能够扫描Spring组件并自动配置Spring Boot,那么它到底是如何自动配置Spring Boot的呢?下面我们通过查看@SpringBootApplication内部源码进行分析,核心代码如下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 表明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan( // 包扫描器
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ...
}
从上述源码可以看出@SpringBootApplication注解是一个组合注解,包含@SpringBootConfiguration、@EnableAutoConfiguration、 @ComponentScan三个核心注解,关于这三个核心注解的相关说明如下。
当以后我们在定义一个作用于类的注解时候,如果希望该注解也作用于其子类,那么可以用@Inherited来进行修饰。
2.1 @SpringBootConfiguration注解
@SpringBootConfiguration注解表示Spring Boot配置类。查看@SpringBootConfiguration注解源码,核心代码如下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
从上述源码可以看出@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式)并可以被组件扫描器扫描。可见@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。
2.2 @EnableAutoConfiguration注解
@EnableAutoConfiguration注解表示开启自动配置功能,该注解是Spring Boot框架最重要的注解,也是实现自动化配置的注解。同样,查看该注解内部源码信息,核心代码加下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包
@Import({AutoConfigurationImportSelector.class}) // 自动配置类扫描导入
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class[] exclude() default {};
String[] excludeName() default {};
}
从上述源码可以看出,@EnableAutoConfiguration注解是一个组合注解,它主要包括有@AutoConfigurationPackage和@Import两个核心注解。下面我们对这两个核心注解分别讲解。
1.@AutoConfigurationPackage注解
查看@AutoConfigurationPackage注解内部源码信息,核心代码如下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class[] basePackageClasses() default {};
}
从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,作用是向容器导入注册的所有组件,导入的组件由Registrar决定。查看Registrar类源码信息,核心代码如下。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目,会发现上述代码中加粗部分获取的是项目主程序启动类所在的目录com.cy。也就是说,@AutoConfigurationPackage注解的主要作用是获取项目主程序启动类所在根目录,从而指定后续组件扫描器要扫描的包位置。因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。
2.@Import({AutoConfigurationImportSelector.class})注解
查看AutoConfigurationImportSelector类的getutoOonfiguratinEntry)方法,核心代码如下。
上述展示的getAutoConfigurationEntry()方法,其主要作用是筛选出当前项目环境需要启动的自动配置类XxxAutoConfiguration,从而实现当前项目运行所需的自动配置环境。
this.getCandidateConfigurations(annotationMetadata, attributes)方法:该方法的主要作用是从Spring Boot提供的自动配置依赖META-INF/spring.factories文件中获取所有候选自动配置类XxxAutoConfiguration。
this.getConfigurationClassFilter().filter(configurations)方法:该方法的作用是对所有候选的自动配置类进行筛选,根据pom.xml项目文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类。
为了让初学者更清楚地知道META-INF/spring.factories类路径下META-INF下的spring.factores文件中Spring Boot提供的候选自动配置类XxxAutoConfiguration有哪些,这里以Spring Boot项目结构为例进行展示说明,具体如下图所示。
同样以项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConiguration自动配置类就会生效。打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对Spring MVC运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC校验器等。而这些自动配置类的本质是传统Spring MVC框架中对应的XML配置文件,只不过在SpringBoot中以自动配置类的形式进行了预先配置。因此,在SpringBoot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序。当然,我们也可以对这些自动配置类中默认的配置进行更改。
2.3 @ComponentScan注解
@ComponentScan注解是一个组件包扫描器,用于将指定包中的注解类自动装配到Spring的Bean容器中。@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项目主程序启动类所在包的具体位置。
3.Spring Boot执行流程
每个SpringBoot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过SpringApplication.run()的执行即可启动整个Spring Boot程序。那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?下面我们查看run()方法内部的源码,核心代码如下。
public static ConfigurableApplicationContext run(Class primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实例的初始化和调用run()启动项目,这两个阶段的实现具体说明如下。
3.1 SpringApplication实例的初始化
查看SpringApplication实例对象初始化的源码信息,核心代码如下。
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。
(1) this.webApplicationType = WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)。
(2) this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
用于设置SpringApplication应用的初始化器。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factories文件中获取所有可用的应用初始化器类ApplicationContextInitializer。
(3) this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
用于设置SpringApplication应用的监听器。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factories文件中获取所有可用的监听器类ApplicationListener。
(4) this.mainApplicationClass = this.deduceMainApplicationClass()
用于推断并设置项目main()方法启动的主程序启动类。
3.2 项目的初始化启动
分析完new SpringApplication(primarySources).run(args)源码前一部分SpringAplication实例对象的初始化后,查看run(args)方法执行的项目初始化启动过程,核心代码如下。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
// 1.获取SpringApplication初始化的SpringApplicationRunListeners运行监听器并运行
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2.项目运行环境Environment的预配置
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
// 3.项目应用上下文ApplicationContext的预配置
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 4.由项目运行监听器启动配置好的应用上下文ApplicationContext
listeners.started(context);
// 5.调用应用上下文ApplicationContext中配置的程序执行器XxxRunner
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
// 6.由项目运行监听器持续运行配置好的应用上下文ApplicationContext
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
从上述源码可以看出,项目初始化启动过程大致包括以下6部分。
(1) this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
(2) this.prepareEnvironment(listeners, bootstrapContext, applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境。
(3) this.createApplicationContext()方法及下面部分代码,主要作用是对项目应用上下文ApplicationContext的预配置,包括先创建应用上下文环境ApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置。
(4) listeners.started(context)方法用于使运行监听器SpringApplicationRunListener启动配置好的应用上下文ApplicationContext。
(5) this.callRunners(context, applicationArguments)方法用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。 其中,Spring Boot提供的执行器接口有ApplicationRunner和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,Spring Boot项目启动后即会立即执行这些特定程序。
(6) listeners.running(context)方法表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,这样整个Spring Boot项目就正式启动完成了。与此同时,经过初始化封装设置的应用上下文ApplicationContext也处于活跃状态。
下面我们通过一个Spring Boot执行流程图,来让大家更清晰地知道Spring Boot的整体执行流程和主要启动阶段,具体如下图所示:
相关推荐HOT
更多>>如何添加Java环境变量?
要添加Java环境变量,可以按照以下步骤:并安装Java开发工具包(JDK)、找到Java安装路径、设置JAVA_HOME环境变量、添加Java可执行文件路径到PATH...详情>>
2023-05-04 11:00:56从零开始学Java之String字符串的编码
对很多小白来说,可能不明白什么是字符编码,也不知道为什么要有字符编码,所以先来给大家简要地介绍一下字符编码。详情>>
2023-05-04 10:21:02新手速来!几步带你掌握MyBatis Plus
Mybatis-Plus(简称MP)是一款Mybatis的增强工具,它是在Mybatis的基础上实现的简化开发工具。Mybatis-Plus给我们提供了开箱即用的CRUD操作、自动...详情>>
2023-04-28 10:57:09学习java需要什么基础?基础知识有哪些?
网络编程:了解基本的网络编程概念和协议,熟悉 Java 网络编程 API。建议在学习 Java 之前,先学习一些基础的编程语言,如 C 或 Python 等,这...详情>>
2023-04-28 10:41:14