Spring Boot原理剖析和源码分析
依赖管理
问题一:为什么导入dependency时不需要指定版本?
spring-boot-starter-parent依赖
<!-- Spring Boot 父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.3.0.RELEASE,该版本号根据实际开发需求可以修改。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
继续查看spring-boot-dependencies底层源文件
<properties>
<activemq.version>5.15.12</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.80</appengine-sdk.version>
<artemis.version>2.12.0</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.16.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.2</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.1.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.10</byte-buddy.version>
<caffeine.version>2.8.2</caffeine.version>
<cassandra-driver.version>4.6.1</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.14</commons-codec.version>
......
</properties>
从spring-boot-dependenices底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,如activemq、spring、tomcat等,都有与Spring Boot2.3.0.RELEASE版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。
问题二:spring-boot-starter-parent伏以来启动器的主要作用是进行版本统一管理,那么项目运行依赖的jar包是从何而来?
spring-boot-starter-web
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.9.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
从上述可以看出,spring-boot-starter-web依赖启动器的主要作用是提供web开发场景所需要的底层所有依赖。
版本由spring-boot-starter-parent统一进行管理。
image-20200526100443828.pngSpring Boot官网提供了部分场景的依赖启动器,这些依赖适用与不同的开发场景,适用时直接在pom.xml中导入即可。
但是Spring Boot官网并不是针对所有的场景的开发技术框架都提供了依赖启动器,如mybatis、druid等,但是为了充分利用Spring Boot框架的优势,mybatis、druid等技术框架团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器。mybatis-spring-boot-starter、druid-spring-boot-starter。在需要的时候直接在pom.xml文件中导入即可,但是需要自己管理版本号。
自动配置(启动流程)
概念:能够在我们添加jar包依赖时,自动为我们进行配置一下配置,我们可以不需要配置或者少量配置就能运行编写的项目。
问题:Spring Boot到底是如何进行自动配置的,都把那些组件进行了自动配置?
Spring Boot 应用启动的入口是@SpringBootApplication注解标注类的main方法,
@SpringBootApplication能够扫描Spring组件并且自动配置Spring Boot
@SpringBootApplication
public class RegistryApplication {
public static void main(String[] args) {
SpringApplication.run(RegistryApplication.class);
}
}
@SpringBootApplication注解类
// 注解的适用范围:类、接口、枚举
@Target({ElementType.TYPE})
// 注解的生命周期:运行时
@Retention(RetentionPolicy.RUNTIME)
// 标明注解可标注在javadoc中
@Documented
// 标明注解可以被子类继承
@Inherited
// 标明该类为配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
// 包扫描器
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
从上面可以看出,@SpringBootApplication注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个核心注解组成。
@SpringBootConfiguration注解
@SpringBootConfiguration注解表示为Spring Boot配置类
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 配置到IOC容器
@Configuration
public @interface SpringBootConfiguration {
}
从上述可以看出,@SpringBootConfiguration注解类主要注解为@Configuration注解,该注解由Spring框架提供,表示当前类为一个配置类,并且可以被组件扫描器扫描。
@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注解为一个组合注解,其作用就是借助@Import注解导入特定场景需要向IOC注册的Bean,并且加载到IOC容器。@AutoConfigurationPackage就是借助@Import来搜集所有符合自动配置条件的Bean定义,并且加载到IOC容器中。
(1)@AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入Registrar中注册的组件
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
从源码可以看出,@AutoConfigurationPackage注解的功能由@Import注解实现,它是Spring框架底层注解,它的作用就是给容器导入某个组件类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 将主程序类所在的包以及所有子包下的组件扫描到Spring容器
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}
从上述可以看出,@AutoConfigurationPackage注解的主要作用就是将主程序类所在的包以及所有子包下的组件加载到IOC容器中。
因此:在定义项目包目录时,要求定义的包结构必须规范,项目主程序启动类要放在最外层的根目录位置,然后在根目录的位置内部建立子包和类进行业务开发,这样才能保证定义的类才能被组件扫描器扫描。
(2)@Import({AutoConfigurationImportSelector.class})
将 AutoConfigurationImportSelector 类导入到Spring容器中。AutoConfigurationImportSelector 可以帮助Spring容器将所有符合条件的@Configuration配置都加载到Spring Boot创建并使用的IOC容器(ApplicationContext)中。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取自动配置的元数据,需要传入beanClassLoader这个类加载器
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 从META-INF/spring.factories配置文件中将对于的自动配置类获取到
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
@EnableAutoConfiguration注解就是从classpath中搜寻META-INF/spring.factories配置文件,并将其org.springframework.boot.autoconfigure.EnableAutoConfiguration对于的配置通过反射实例化对应的标注了@Configuration的JavaConfig配置类,并且加载到IOC容器中。
以web项目为例,在项目中加入了web环境依赖启动器,对应的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration自动配置就会生效,打开自动配置就会发现,在配置类中通过全注解的方式对 Spring MVC 运行环境所需要环境进行了默认配置,包括前缀、后缀、试图解析器、MVC校验器等。
总结
Spring Boot底层实现自动配置的步骤:
-
spring boot 应用启动
-
@SpringBootApplication起作用
-
@EnableAutoConfiguration
-
@AutoConfigurationPackage
这个注解主要作用就是@Import({Registrar.class}),它通过Registrar类导入容器中,而Registrar的作用就是将扫描主配置类的包以及子包,并将对应的组件导入IOC容器中。
-
@Import({AutoConfigurationImportSelector.class})
它将 AutoConfigurationImportSelector 类导入容器中,AutoConfigurationImportSelector 类的作用是通过selectImports()方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有的jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给Spring Factory加载器进行一系列的容器创建过程。
(3)@ComponentScan
@ComponentScan注解具体扫描包的路径,由Spring Boot主程序所在包的位置决定。在扫描的过程中由@AutoConfigurationPackage注解进行解析,从而得到Spring Boot主程序类所在包的具体位置
网友评论