我们都知道,
Spring
有它强大的地方,也有它繁琐的地方,毕竟如日中天的
Spring
全家桶太强大了,所以导致依赖各种
JAR
包维护起来费劲,还有编写各种
XML
配置文件。这两个痛点
SpringBoot
可以优雅的实现解决。背后当然是
SpringBoot
的
约定优于配置(Convention over Configuration)
,又称
按约定编程
,是
一种软件设计范式
。
SpringBoot
是所有基于
Spring
开发的项目的起点。
SpringBoot
的设计就是为了让你尽可能快的跑起来
Spring
应用程序并且尽可能减少你的配置文件。
了解了
SpringBoot
出现的背景及优势后,我们就开始从下面几个方面来分析
SpringBoot
的原理以及源码分析。
问题1:为什么导入dependency时不需要指定版本?
在SpringBoot入门程序中,项目pom.xml文件中有两个核心依赖:
spring-boot-starter-parent
1、spring-boot-starter-parent
< parent > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-starter-parent artifactId > < version > 2.3.1.RELEASE version > < relativePath /> parent >
我们再使用“
ctrl+鼠标左键
”进入并查看
spring-boot-starter-parent
底层源文件,发现spring-boot-starter-parent的底层有一个父依赖
spring-boot-dependencies
,如下:
< parent > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-dependencies artifactId > < version > 2.3.1.RELEASE version > parent >
继续查看
spring-boot-dependencies
的源文件:
5.15.12 2.7.7 1.9.80 2.12.0 1.9.5 3.16.1 4.0.6 4.0.3 2.1.4 // ...
从
spring-boot-dependencies
的源文件可以看出,该文件通过标签对一些常用的技术框架的依赖文件进行了统一的版本号管理,例如
activemq、spring、tomcat
等,都有与
Spring Boot 2.3.1
版本相匹配的版本,这也是
pom.xml
引入依赖文件不需要标注依赖文件版本号的原因。
需要注意的是,如果pom.xml引入的依赖文件不是
spring-boot-starter-parent
管理的,那么在
pom.xml
引入依赖文件时,需要使用标签指定依赖文件的版本号。
问题2:
spring-boot-starter-parent
父依赖启动器的主要作用是进行统一的版本管理,那么项目运行依赖的
JAR
包从何而来?
2 spring-boot-starter-web
查看
spring-boot-starter-web
的源文件:
< dependencyManagement > < dependencies > < dependency > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-dependencies artifactId > < version > 2.3.1.RELEASE version > < type > pom type > < scope > import scope > dependency > dependencies > dependencyManagement > < dependencies > < dependency > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-starter artifactId > < version > 2.3.1.RELEASE version > < scope > compile scope > dependency > < dependency > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-starter-json artifactId > < version > 2.3.1.RELEASE version > < scope > compile scope > dependency > < dependency > < groupId > org.springframework.boot groupId > < artifactId > spring-boot-starter-tomcat artifactId > < version > 2.3.1.RELEASE version > < scope > compile scope > dependency > < dependency > < groupId > org.springframework groupId > < artifactId > spring-web artifactId > < scope > compile scope > dependency > < dependency > < groupId > org.springframework groupId > < artifactId > spring-webmvc artifactId > < scope > compile scope > dependency > dependencies >
Spring Boot
除了提供有上述介绍的
Web
依赖启动器外,还提供了其他许多开发场景的相关依赖。我们可以打开
Spring Boot
的官方文档,搜索
starters
关键字查询场景依赖启动器。
Spring Boot
并不是提供了所有场景的开发的技术框架都提供了场景启动器,例如
MyBatis、Druid
等。为了利用
Spring Boot
框架的优势,在
Spring Boot
官方没有整合这些技术框架的情况下,
MyBatis、Druid
等技术框架所在的开发团队主动与
Spring Boot
框架进行整合,实现了各自的依赖启动器。
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目。
问题:
Spring Boot
到底是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot
应用的启动入口是
@SpringBootApplication
注解标注类中的
main()
方法,
@SpringBootApplication
能够扫描
Spring
组件并自动配置
Spring Boot
。
下面,查看
@SpringBootApplication
内部源码进行分析,核心代码具体图如下:
@SpringBootApplication public class SpringBootExampleApplication { public static void main (String[] args) { SpringApplication.run(SpringBootExampleApplication . 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 //配置类 public @interface SpringBootConfiguration { }
可以看出,
@SpringBootConfiguration
注解内部有一个核心注解
@Configuration
,该注解是由Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解形式),并可以被组件扫描器扫描。由此可见,
@SpringBootConfiguration
注解的作用与
@Configuration
注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过
@SpringBootConfiguration
是被
Spring Boot
进行了重新封装命名而已。
2、@EnableAutoConfiguration
@EnableAutoConfiguration
注解表示开启自动配置功能,该注解是
Spring Boot
框架最重要的注解,也是实现自动化配置的注解。我们同样来查看该注解的源代码:
@Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage // 自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中 @Import (AutoConfigurationImportSelector . class ) // 可以帮助 springboot 应用将所有符合条件的@ Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器( ApplicationContext )中 public @ interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration" ; Class>[] exclude() default {}; String[] excludeName() default {}; }
下面,对这两个核心注解分别讲解:
2.1 @AutoConfigurationPackage
@Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited // spring框架的底层注解,它的作用就是给容器中导入某个组件类, // 例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中 @Import (AutoConfigurationPackages.Registrar . class ) // 默认将主配置类(@ SpringBootApplication )所在的包及其子包里面的所有组件扫描到 Spring 容器中 public @ interface AutoConfigurationPackage { }
@Import(AutoConfigurationPackages.Registrar.class)
,它就是将
Registrar
这个组件类导入到容器中,可查看
Registrar
类中
registerBeanDefinitions
方法,这个方法就是导入组件类的具体实现。
static class Registrar implements ImportBeanDefinitionRegistrar , DeterminableImports { // 获取的是项目主程序启动类所在的目录 // metadata:注解标注的元数据信息 @Override public void registerBeanDefinitions (AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件 register(registry, new PackageImport(metadata).getPackageName()); } @Override public Set determineImports (AnnotationMetadata metadata) { return Collections.singleton( new PackageImport(metadata)); } }
使用debug跟踪可以发现
new PackageImport(metadata).getPackageName()
就是
com.riemann
。
也就是说
@AutoConfigurationPackage
注解的主要的作用就是将主程序类所在包及所有子包下的组件扫描到
Spring
容器中。
因此,在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类运行业务开发,这样才能保证定义的类能够被组件扫描器扫描。
2.2 @Import(AutoConfigurationImportSelector.class)
将
AutoConfigurationImportSelector
这个类导入到
Spring
容器中,
AutoConfigurationImportSelector
可以帮助
Spring Boot
应用将所有符合条件的
@Configuration
配置都加载到当前
Spring Boot
创建并使用的
IoC
容器(
ApplicationContext
)中,通过源码分析这个类是通过
selectImports
这个方法告诉
Spring Boot
都需要导入哪些组件:
// 这个方法告诉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); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
深入研究
loadMetadata
方法:
final class AutoConfigurationMetadataLoader { 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 urls = (classLoader != null ) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); // 遍历 URL 数组,读取到 properties 中 Properties properties = new Properties(); // 2.解析urls枚举对象中的信息封装成properties对象并加载 while (urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils.loadProperties( new UrlResource(urls.nextElement()))); } // 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象 // 根据封装好的properties对象生成AutoConfigurationMetadata对象返回 return loadMetadata(properties); } catch (IOException ex) { throw new IllegalArgumentException( "Unable to load @ConditionalOnClass location [" + path + "]" , ex); } } ... }
深入研究
getAutoConfigurationEntry
方法
protected AutoConfigurationEntry getAutoConfigurationEntry (AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { // 1. 判断是否开启注解。如未开启,返回空串 if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 2. 获得注解的属性 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表 // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories, // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称, // 将这些值作为自动配置类导入到容器中,自动配置类就生效了 List configurations = getCandidateConfigurations(annotationMetadata, attributes); // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的 configurations = removeDuplicates(configurations); // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置, // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。 //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性) Set exclusions = getExclusions(annotationMetadata, attributes); // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常) checkExcludedClasses(configurations, exclusions); // 4.2 从 configurations 中,移除所有不希望自动配置的配置类 configurations.removeAll(exclusions); // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类 //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。 //@ConditionalOnMissingClass : classpath中不存在该类时起效 //@ConditionalOnBean : DI容器中存在该类型Bean时起效 //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效 //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效 //@ConditionalOnExpression : SpEL表达式结果为true时 //@ConditionalOnProperty : 参数设置或者值一致时起效 //@ConditionalOnResource : 指定的文件存在时起效 //@ConditionalOnJndi : 指定的JNDI存在时起效 //@ConditionalOnJava : 指定的Java版本存在时起效 //@ConditionalOnWebApplication : Web应用环境下起效 //@ConditionalOnNotWebApplication : 非Web应用环境下起效 // 总结一下判断是否要加载某个类的两种方式: // 根据spring-autoconfigure-metadata.properties进行判断。 // 要判断@Conditional是否满足 // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。 configurations = filter(configurations, autoConfigurationMetadata); // 6. 将自动配置导入事件通知监听器 // 当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类, // 并触发fireAutoConfigurationImportEvents事件。 fireAutoConfigurationImportEvents(configurations, exclusions); // 7. 创建 AutoConfigurationEntry 对象 return new AutoConfigurationEntry(configurations, exclusions); }
getAutoConfigurationEntry
里有一个重要的方法
getCandidateConfigurations
,这个方法是让
SpringFactoriesLoader
去加载一些组件的名字。
protected List getCandidateConfigurations (AnnotationMetadata metadata, AnnotationAttributes attributes) { // 让SpringFactoryLoader去加载一些组件的名字 List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); // 断言,非空 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct." ); return configurations; } protected Class> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration . class ; } protected ClassLoader getBeanClassLoader () { return this .beanClassLoader; }
该方法主要市区加载一个外部文件,该文件在如下图:
@EnableAutoConfiguration
就是从
classpath
中搜寻
META-INF/spring.factories
配置文件,并将其中
org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的配置项通过反射(
Java Reflection
)实例化为对应的标注了
@Configuration的JavaConfig
形式的配置类,并加载到
IoC
容器中。
直到看到了
spring.factories
配置文件对应的那些类才明白了
Spring Boot
为啥不需要配置各种
XML
文件了。那是因为这些配置类的本质是传统
Spring MVC
框架中对应的
XML
配置文件,只不过在
Spring Boot
中以自动配置类的形式进行了预先配置。
因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改。
2.3 小结
Spring Boot
底层实现自动配置的步骤是:
@SpringBootApplication
起作用;
@SpringBootConfiguration
,标明该类为配置类;