传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件等;相较而言,SpringBoot框架显得更加方便、快捷和高效。那么Spring Boot框架究竟如何做到这些呢?下面袁老师分别针对SpringBoot框架的依赖管理、自动配置和执行流程进行深入分析和介绍。
1.1 spring-boot-starter-parent依赖
我们在创建的Spring Boot项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下。
org.springframework.boot
spring-boot-starter-parent
2.7.5
其中spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理。使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码如下:
org.springframework.boot
spring-boot-dependencies
2.7.5
继续查看spring-boot-dependencies底层源文件,核心代码如下。
5.16.5
2.7.7
1.9.98
2.19.1
1.9.7
3.22.0
4.0.6
4.2.0
3.3.0
1.12.18
2.6.1.Final
2.9.3
4.14.1
1.5.1
1.15
2.9.0
3.12.0
1.6
2.11.1
...
从spring-boot-dependencies底层源文件可以看出,该文件通过
1.2 spring-boot-starter-web依赖
spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来,又是怎样管理的呢?下面查看项目pom.xml文件中的spring-boot-starter-web依赖文件源码,核心代码如下。
org.springframework.boot
spring-boot-starter
2.7.5
compile
org.springframework.boot
spring-boot-starter-json
2.7.5
compile
org.springframework.boot
spring-boot-starter-tomcat
2.7.5
compile
org.springframework
spring-web
5.3.23
compile
org.springframework
spring-webmvc
5.3.23
compile
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖文件,它对Web开发场景所需的依赖文件进行了统一管理。
正是如此,在pom.xm中引入sring-bo-tartr -web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring- boot-starter-parent父依赖进行统一管理。
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
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目,会发现上述代码中加粗部分获取的是项目主程序启动类所在的目录com.cy。也就是说,@AutoConfigurationPackage注解的主要作用是获取项目主程序启动类所在根目录,从而指定后续组件扫描器要扫描的包位置。因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。
2.@Import({AutoConfigurationImportSelector.class})注解
查看AutoConfigurationImportSelector类的getutoOonfiguratinEntry)方法,核心代码如下。
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取所有Spring Boot提供的后续自动配置类XxxAutoConfiguration
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 筛选并过滤出当前应用环境下需要的自动配置类XxxAutoConfiguration
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
上述展示的getAutoConfigurationEntry()方法,其主要作用是筛选出当前项目环境需要启动的自动配置类XxxAutoConfiguration,从而实现当前项目运行所需的自动配置环境。
为了让初学者更清楚地知道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项目主程序启动类所在包的具体位置。
每个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部分。
下面我们通过一个Spring Boot执行流程图,来让大家更清晰地知道Spring Boot的整体执行流程和主要启动阶段,具体如下图所示:
下面我们来对本节内容做个小结,好程序员袁老师带着大家主要学习了:深入分析了Spring Boot的原理,包括依赖管理、自动配置和执行流程。通过本章的学习,大家应该对Spring Boot有一个初步认识,为后续学习Spring Boot做好铺垫。好了这一章节的内容好程序员袁老师就给大家介绍到这里,有项目实战的可以后台dd小源!