SpringBoot源码解析(一)

时间:2024-11-09 07:03:36

SpringBoot自动装配原理

@SpringBootApplication注解

我们在使用SpringBoot时,通常使用的是@SpringBootApplication这个注解,比如:

而这个注解的定义为下图,可以发现这个注解上有另外三个注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,所以我们可以认为@SpringBootApplication是一个三合一注解;

所以我们也可以这么用,如果我们这么用就能自己控制要不要用@EnableAutoConfiguration这个注解,如果用就表示开启自动配置,如果不用就表示不开启自动配置,那开启和不开启自动配置到底该怎么理解呢?

SpringBoot的自动配置就是SpringBoot自动配置一些Bean,从而让开发人员在用SpringBoot时可以少去配置很多Bean,所以如果我们开启了自动配置,那最终Spring容器中就有SpringBoot帮我们配置的Bean,如果没有开启自动配置,那Spring容器中就没有这些Bean,就需要我们自己去配置。

@EnableAutoConfiguration

那我们来看看@EnableAutoConfiguration这个注解是如何工作的?先看注解源码定义:

其中非常核心的就是AutoConfigurationImportSelector,而AutoConfigurationImportSelector实现了DeferredImportSelector这个接口,Spring容器在启动时,会在解析完其他所有程序员定义的配置类之后,来调用AutoConfigurationImportSelector中的selectImports方法,然后把该方法返回的类名对应的类作为配置类进行解析。

该方法会利用SpringFactoriesLoader找到所有的META-INF/spring.factories文件中key为
EnableAutoConfiguration.class的value值,也就是众多自动配置类的类名。

拿到这些类名后会进行去重,去重完之后,就会看是否存在某些自动配置类需要排除,我们可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude配置来指定一些自动配置类的名字,然后把它们从自动配置类集合中排除掉。

然后会继续利用ConfigurationClassFilter对自动配置类进行进一步筛选,ConfigurationClassFilter会利用AutoConfigurationMetadata进行筛选,而AutoConfigurationMetadata对象对应的是"METAINF/ spring-autoconfigure-metadata.properties"文件中的内容,这是一种加快SpringBoot启动速度的机制,默认是开启了的。不过要通过maven或gradle的方式引入springboot的依赖来使用才能看 到效果,因为这个文件的内容是在SpringBoot源码工程编译的时候自动生成出来的,当然我们也可以手动创建这个文件,以及这个文件的内容,自动生成的这个文件内容样例如下

内容格式为:自动配置类名.条件注解=条件

有了这个文件的内容,SpringBoot会在通过spring.facotries文件找到所有的自动配置类后,会把这个文件中的内容读出来,然后利用AutoConfigurationImportFilter对所有的自动配置类进行条件匹配,这里的条件判断,只会判断所需要的类是否存在。如果需要的类或者需要的Bean对应的类都不存在,那么肯定不符合条件了,对于像@ConditionalOnMissingBean这样的条件,在这一步是不会去判断的,最后条件匹配成功的自动配置类就会记录下来,并最终返回给Spring容器,继续进行其他条件的匹配。所以通过这个机制,使得Spring并不需要解析所有的自动配置类,从而提高了效率。

当然在这个过滤的过程中,如果日志级别等于trace级别,那么会把所有条件不匹配的自动配置类记录到日志中,如果日志框架配置了打印到控制台,那就会打印到控制台,比如:

在SpringBoot中,还有一个更加强大的统计自动配置类匹配结果的功能,就是可以配置
debug=true,只要开启了这个配置,那么Spring在解析每一个自动配置类时,就会将是否匹配的结
果进行记录,比如开启了debug=true,我们可以在控制台看到

可以看到这个匹配结果分别记录了:

1. 哪些自动配置类的条件是匹配的

2. 哪些自动配置类的条件是不匹配的,并且具体原因也会打印出来,比如是哪个类不存在

3. 哪些自动配置类是无条件的

这个功能的实现,是Spring解析具体的自动配置类上的各种条件注解的时候统计的,每解析一个条件注解,就会把结果记录在ConditionEvaluationReport对象中,当Spring容器启动完成后,会发布一个ContextRefreshedEvent事件,而SpringBoot提供了一个ConditionEvaluationReportLoggingListener会处理这个事件,接收到这个事件后就会把统计结果进
行打印。

自动配置类解析的大体流程为:

1. 读取spring.factories中的所有自动配置类

2. 看是否配置了需要排除的自动配置类,进行排除

3. 然后利用spring-autoconfigure-metadata.properties文件来过滤掉一些自动配置类(条件中指定的类不存在的自动配置类)

4. 解析过滤后自动配置类,判断自动配置类所有的条件注解,条件全部符合才会真正去解析自动配置类上的其他内 容,比如@Bean(也会进行条件判断)。

@SpringBootConfiguration

可以看到@SpringBootConfiguration就是对@Configuration类的简单包装,这个注解在之前的spring源码解析中已经解释的很清楚了。

@ComponentScan

componetScan注解想必也不陌生,在之前的spring源码解析中也已经解释的很清楚了。

条件注解原理

springboot中的条件注解

1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
4. ConditionalOnClass:是否存在某个类
5. ConditionalOnMissingClass:是否缺失某个类
6. ConditionalOnExpression:指定的表达式返回的是true还是false
7. ConditionalOnWebApplication:当前应用是一个Web应用
8. ConditionalOnNotWebApplication:当前应用不是一个Web应用
9. ConditionalOnProperty:Environment中是否存在某个属性
10. ConditionalOnResource:指定的资源是否存在

当然我们也可以利用@Conditional来自定义条件注解。条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不会生效就要看当前条件能不能符合,或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。

@Condional的原理和源码

从condition 的使用需求我们知道,这个是单条件满足的时候才实例化bean 和加入到spring 容器,而在spring中一个类的实例化必须要变成beanDefinition对象,而ConfigurationClassPostProcessor 是所有beanDefinition 对象的集散地,所有的beanDefinition 都会在这个类里面处理。那么我们要完成Condition 功能也必定在这个类里面,ConfigurationClassPostProcessor 类中的shouldSkip 方法就是做bean 过滤的。
 

我们可以发现,SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。

这个getMatchOutcome 是一个钩子方法,不同的注解调用的实现类不一样,这里看两个注解的实现

1、conditionalOnBean

Bean 存在时才掉用方法,这个其实很好理解,判断bean 是否存在其实就只要从BeanFactory 中找就行了,源码里面就是从BeanFactory 中找。

从BeanFactory 中获取对应的实例,如果有则匹配。

2、conditionalOnClass

当工程上下文中存在该类时才调用方法,实现原理就是通过Class.forName反射的方式,如果反射有异常则返回false,如果反射没异常返回true

Starter机制

Starter原理

那SpringBoot中的Starter和自动配置又有什么关系呢?

其实首先要明白一个Starter,就是一个Maven依赖,当我们在项目的pom.xml文件中添加某个Starter依赖时,其实就是简单的添加了很多其他的依赖,比如:

1、spring-boot-starter-web:引入了spring-boot-starter、spring-boot-starter-json、spring-boot-starter-tomcat等和Web开发相关的依赖包
2、spring-boot-starter-tomcat:引入了tomcat-embed-core、tomcat-embed-el、tomcat-embed-websocket等和Tomcat相关的依赖包

如果硬要把Starter机制和自动配置联系起来,那就是通过@ConditionalOnClass这个条件注解,因为这个条件注解的作用就是用来判断当前应用的依赖中是否存在某个类或某些类,比如:

上面代码中就用到了@ConditionalOnClass,用来判断项目中是否存在Servlet.class、
Tomcat.class、UpgradeProtocol.class这三个类,如果存在就满足当前条件,如果项目中引入了
spring-boot-starter-tomcat,那就有这三个类,然后机会将TomcateServletWebServerFactory假如到spring容器中,最终会调用getWebServer方法获取到web容器。

这个代码中就用到了@ConditionalOnMissingBean,意思是如果当前不存在 ServletWebServerFactory类型的Bean,那就符合条件,结合整体代码意思就是:如果用户自己没有定义ServletWebServerFactory类型的Bean,那代码中所定义的Bean就会生效;如果用户自己定义了ServletWebServerFactory类型的Bean,那代码中定义的Bean就不生效;所以这个注解是非常重要的,SpringBoot利用这个注解来决定到底用用户自己的Bean,还是用 SpringBoot自动配置的。

如果没有spring-boot-starter-tomcat那就可能没有这三个类(除非你自己单独引入了Tomcat相关的依赖)。所以这就做到了,如果我们在项目中要用Tomcat,那就依赖spring-boot-starter-web就够了,因为它默认依赖了spring-boot-starter-tomcat,从而依赖了Tomcat,从而Tomcat相关的Bean能生效。

而如果不想用Tomcat,那就得这么写:得把spring-boot-starter-tomcat给排除掉,再添加上spring-boot-starter-jetty的依赖,这样Tomcat的Bean就不会生效,Jetty的Bean就能生效,从而项目中用的就是Jetty。

自定义Starter

当公司里面需要把一些共用的api 封装成jar 包的时候,就可以尝试自定义启动器来做。自定义启动器用到的就是springboot中的SPI 原理,springboot 会去加载META-INF/spring.factories 配置文件,并加载EnableAutoConfiguration 为key的所有类。

利用这一点,我们定义一个工程也会有这个文件。
1、定义启动器核心工程,工程结构如下

spring.factories 配置内容

被springboot SPI 加载的类

这个RedisTemplate 实例就是我们封装的通用API,其他工程可以直接导入jar使用的。

2、自定义starter
我们还会定义一个没代码的工程,在这个工程里面没有任何代码,只有一个pom文件,Pom 里面就是对前面核心工程jar 包的导入,工程名定义为spring-boot-study-starter

3、自定义启动器使用
其实就只要在另外的springboot 工程pom 文件里面导入依赖就可以了,这个依赖就是自定义starter 那个工程的maven 坐标。

总结

SpringBoot启动时,最核心的也就是创建一个Spring容器,而创建Spring容器的过程中会注解做几件事情:

一、把SpringApplication.run(SpringBootExample.class)传入进来的MyApplication类做为配置类进行解析。
二、由于MyApplication类上定义了@SpringBootApplication,相当于定义了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan注解。
三、所以SpringBoot会进一步解析这些注解@EnableAutoConfiguration,通过@import注解导入AutoConfigurationImportSelector这个配置类,因为它实现了DeferredImportSelector接口,所以Spring会在把其他配置类都解析完之后,在最后才解析AutoConfigurationImportSelector这个配置类(Spring Framework中的知识);

四、而AutoConfigurationImportSelector这个类的作用就是用来解析SpringBoot的自动配置类,那既然无法扫描到SpringBoot中的自动配置类,那怎么知道SpringBoot中有哪些自动配置类呢?默认情况下SpringBoot会提供一个spring.factories文件,并把所有自动配置类的名字记录在这个文件中,SPI 在springboot 中是去读取META-INF/spring.factories 目录的配置文件内容,把配置文件中的类加载到spring 容器中。如果你想把一个类加载到spring 容器中,也可以采用这种方式来做。把类配置到spring.factories 配置文件中即可。并且这件事也是发生在解析完用户的配置类之后的,那么我们总结一下,如果你想把一个类加载到spring 容器中管理有几种方式:

1、通过xml 的bean 标签;

2、通过加@Component 注解被@ComponentScan 扫描;

3、通过在spring.factories 配置该类前两者是加载本工程的bean,扫描本工程的bean,第三点可以加载第三方定义的jar 包中的bean,毕竟第三方jar 包的包名跟本工程包名可能不一样,所以前两个方式扫描不到。
四、@ComponentScan:扫描,扫描时会扫描到用户所定义的配置类,并解析用户的配置类,注意:扫描是扫描不到SpringBoot的自动配置的类,因为扫描的包路径不匹配,SpringBoot的包都是
org.springframework.boot.xxxx,用户都是自己的包路径。