SpringBoot原理深⼊及源码剖析
传统的Spring框架实现⼀个Web服务,需要导⼊各种依赖JAR包,然后编写对应的XML配置⽂件
等,相较⽽⾔,Spring Boot显得更加⽅便、快捷和⾼效。那么,Spring Boot究竟如何做到这些的呢?
接下来分别针对Spring Boot框架的依赖管理、⾃动配置和执⾏流程进⾏深⼊分析。
依赖管理
问题:(1)为什么导⼊dependency时不需要指定版本?
在Spring Boot⼊门程序中,项⽬l⽂件有两个核⼼依赖,分别是spring-boot-starterparent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:
1.spring-boot-starter-parent依赖
在chapter01项⽬中的l⽂件中到spring-boot-starter-parent依赖,⽰例代码如下:
<!-- Spring Boot⽗项⽬依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项⽬的统⼀⽗项⽬依赖管理,并
将项⽬版本号统⼀为2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的
使⽤“Ctrl+⿏标左键”进⼊并查看spring-boot-starter-parent底层源⽂件,发现spring-bootstarter-parent的底层有⼀个⽗依赖spring-boot-dependencies,核⼼代码具体如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
继续查看spring-boot-dependencies底层源⽂件,核⼼代码具体如下:
<properties>
<activemq.version>5.15.11</activemq.version>
...
<solr.version>8.2.0</solr.version>
<mysql.version>8.0.18</mysql.version>
<kafka.version>2.3.1</kafka.version>
<spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.4.RELEASE</spring-retry.version>
<spring-security.version>5.2.1.RELEASE</spring-security.version>
<spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.8.RELEASE</spring-ws.version>
<sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
<sun-mail.version>${jakarta-mail.version}</sun-mail.version>
<tomcat.version>9.0.29</tomcat.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-dataattribute.version>
...
</properties>
从spring-boot-dependencies底层源⽂件可以看出,该⽂件通过标签对⼀些常⽤技术框架的依赖⽂件
进⾏了统⼀版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.2版本相匹配的
版本,这也是l引⼊依赖⽂件不需要标注依赖⽂件版本号的原因。
需要说明的是,如果l引⼊的依赖⽂件不是 spring-boot-starter-parent管理的,那么在
spring-boot-starter-web依赖
查看spring-boot-starter-web依赖⽂件源码,核⼼代码具体如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>at.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作⽤是提供Web开发场景所需的底层所有依赖
正是如此,在l中引⼊spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,⽽
不需要额外导⼊Tomcat服务器以及其他Web依赖⽂件等。当然,这些引⼊的依赖⽂件的版本号还是由spring-boot-starter-parent⽗依赖进⾏的统⼀管理。
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,
我们可以打开Spring Boot官⽅⽂档,搜索“Starters”关键字查询场景依赖启动器
列出了Spring Boot官⽅提供的部分场景依赖启动器,这些依赖启动器适⽤于不同的场景开发,使⽤时只需要在l⽂件中导⼊对应的依赖启动器即可。
需要说明的是,Spring Boot官⽅并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架MyBatis、阿⾥巴巴的Druid数据源等,Spring Boot官⽅就没有提供对应的依赖启动器。为了充分利⽤Spring Boot框架的优势,在Spring Boot官⽅没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进⾏了整合,实现了各⾃的依赖启动器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等。我们在l⽂件中引⼊这些第三⽅
的依赖启动器时,切记要配置对应的版本号
⾃动配置(启动流程)
概念:能够在我们添加jar包依赖的时候,⾃动为我们配置⼀些组件的相关配置,我们⽆需配置或者只需要少量配置就能运⾏编写的项⽬
问题:Spring Boot到底是如何进⾏⾃动配置的,都把哪些组件进⾏了⾃动配置?
Spring Boot应⽤的启动⼊⼝是@SpringBootApplication注解标注类中的main()⽅法,
@SpringBootApplication能够扫描Spring组件并⾃动配置Spring Boot
spring ioc注解
下⾯,查看@SpringBootApplication内部源码进⾏分析,核⼼代码具体如下
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@Target({ElementType.TYPE}) //注解的适⽤范围,Type表⽰注解可以描述在类、接⼝、注解或枚举
@Retention(RetentionPolicy.RUNTIME) //表⽰注解的⽣命周期,Runtime运⾏时
@Documented //表⽰注解可以记录在javadoc中
@Inherited //表⽰可以被⼦类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动⾃动配置功能
@ComponentScan( // 包扫描器
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}
从上述源码可以看出,@SpringBootApplication注解是⼀个组合注解,前⾯ 4 个是注解的元数据
信息,我们主要看后⾯ 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、
@ComponentScan三个核⼼注解,关于这三个核⼼注解的相关说明具体如下:
1.@SpringBootConfiguration注解
@SpringBootConfiguration注解表⽰Spring Boot配置类。查看@SpringBootConfiguration注解源
码,核⼼代码具体如下。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置IOC容器
public @interface SpringBootConfiguration {
}
从上述源码可以看出,@SpringBootConfiguration注解内部有⼀个核⼼注解@Configuration,该
注解是Spring框架提供的,表⽰当前类为⼀个配置类(XML配置⽂件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作⽤与@Configuration注解相同,都是标识⼀个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进⾏了重新封装命名⽽已
2.@EnableAutoConfiguration注解
@EnableAutoConfiguration注解表⽰开启⾃动配置功能,该注解是Spring Boot框架最重要的注
解,也是实现⾃动化配置的注解。同样,查看该注解内部查看源码信息,核⼼代码具体如下
可以发现它是⼀个组合注解,Spring 中有很多以Enable开头的注解,其作⽤就是借助@Import来
收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfiguration就是借助@Import
来收集所有符合⾃动配置条件的bean定义,并加载到IoC容器。
下⾯,对这两个核⼼注解分别讲解 :
(1)@AutoConfigurationPackage注解
查看@AutoConfigurationPackage注解内部源码信息,核⼼代码具体如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class}) // 导⼊Registrar中注册的组件
public @interface AutoConfigurationPackage {
}
从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是spring框架的底层注解,它的作⽤就是给容器中导⼊某个组件类,例如
@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导⼊到容器
中,可查看Registrar类中registerBeanDefinitions⽅法,这个⽅法就是导⼊组件类的具体实现 :
从上述源码可以看出,在Registrar类中有⼀个registerBeanDefinitions()⽅法,使⽤Debug模式启
动项⽬,可以看到选中的部分就是com.lagou。也就是说,@AutoConfigurationPackage注解的主要作
⽤就是将主程序类所在包及所有⼦包下的组件到扫描到spring容器中。
因此在定义项⽬包结构时,要求定义的包结构⾮常规范,项⽬主程序启动类要定义在最外层的根⽬录位
置,然后在根⽬录位置内部建⽴⼦包和类进⾏业务开发,这样才能够保证定义的类能够被组件扫描器扫
(2)@Import({AutoConfigurationImportSelector.class}):
将 AutoConfigurationImportSelector这个类导⼊到spring容器中, AutoConfigurationImportSelector可以帮助springboot应⽤将所有符合条件的
@Configuration配置都加载到当前SpringBoot创建并使⽤的IoC容器(ApplicationContext)中继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个⽅法告诉springboot都需要导⼊那些组件:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断 enableautoconfiguration注解有没有开启,默认开启(是否进⾏⾃动装配)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/
/1. 加载配置⽂件META-INF/spring-autoconfigure-metadata.properties,从中获取所有⽀持⾃动配置类的条件
//作⽤:SpringBoot使⽤⼀个Annotation的处理器来收集⼀些⾃动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进⾏配置。
// SpringBoot会将收集好的@Configuration进⾏⼀次过滤进⽽剔除不满⾜条件的配置类
// ⾃动配置的类全名.条件=值
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
Configurations());
}
深⼊研究loadMetadata⽅法
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";//⽂件中为需要加载的配置类的类路径
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
//重载⽅法
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息⽣成urls枚举对象
// 获得 PATH 对应的 URL 们
Enumeration<URL> urls = (classLoader != null) ? Resources(path) : SystemResources(path);
// 遍历 URL 数组,读取到 properties 中
Properties properties = new Properties();
//2.解析urls枚举对象中的信息封装成properties对象并加载
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new Element())));
}
// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象
//根据封装好的properties对象⽣成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
深⼊getCandidateConfigurations⽅法
⽅法中有⼀个重要⽅法loadFactoryNames,这个⽅法是让SpringFactoryLoader去加载⼀些组件的名字。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 让SpringFactoryLoader去加载⼀些组件的名字
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderF
actoryClass(), getBeanClassLoader());
// 断⾔,⾮空
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
继续点开loadFactory⽅法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = Name();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, ptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = Element();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : Set()) {
String factoryClassName = ((String) Key()).trim();
for (String factoryName : StringUtilsmaDelimitedListToStringArray((String) Value())) {
result.add(factoryClassName, im());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
会去读取⼀个 spring.factories 的⽂件,读取不到会表这个错误,我们继续根据会看到,最终路径的长这样,⽽这个是spring提供的⼀个⼯具类public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "METAINF/spring.factories";
}
它其实是去加载⼀个外部的⽂件,⽽这⽂件是在

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。