1.pom.xml
- 通过前面的学习我们知道在项目的pom.xml中,明显指出了我们创建的项目只是一个父工程的一个子model
- 查看父工程的配置
- 查看父工程
- spring-boot-dependencies:用于管理依赖的版本号(版本仓库,所以我们在我们创建的项目中引入依赖的时候不需要指定依赖的版本)、导入项目需要的核心依赖以及导入项目需要的插件
- spring-boot-starter-parent:用于配置资源过滤,以及补充一些插件配置
2.启动器
- 启动器的作用:只要我们配置了一个场景的启动器,那么这个启动器将为我们将在这个场景需要的所有依赖
- spring Boot会将所有场景需要的依赖封装为一个个的启动器,我们要使用哪一个场景就直接使用这个场景的启动器即可
- 比如上图中的web场景和测试场景,都是需要特定的依赖支持才能跑起来的,但是这些场景需要的依赖配置各不相同,但是又因为在springBoot中约定大于配置,所以各个场景下的依赖配置是固定好的,所以我们就通过启动器来帮我们自动加载当前环境需要的所有依赖
- 启动器的意义:我们的顶级父项目的pom.xml相当于一个依赖仓库,它把我们可能使用到的几乎所有依赖全部都配置进去了,但是我们的一个项目不一定会使用到全部的依赖,所以我们创建的工程的pom.xml中的启动器就是一个去依赖仓库中选取我们需要使用哪些依赖的工具,这样我们的项目的依赖就有了定制化的特点
3.入口程序
package com.thhh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //标注这个类是一个springBoot的应用
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);//启动springBoot项目
}
}
1.注解分析
整个入口程序/启动程序的时候虚拟机首先就会读取到注解@SpringBootApplication,这个注解的作用除了表示这个类是一个springBoot的应用程序之外,它还有一个更加重要的作用就是实现springBoot中spring容器的自动装配
- 这个注解的定义中使用了3个比较核心的第3方注解
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
- @SpringBootConfiguration继承了spring中学习过的注解@Configuration,即表示某一个类是一个spring容器的配置类,所以我们的入口程序本身也是spring容器的一个配置类/组件
- @ComponentScan这个类相对于学习了spring之后是不会陌生的,在原来使用xml配置文件的时候,要使用注解实现一个类在配置文件中的自动装配的时候,除了需要在要进行自动装配的类上面使用注解@Component之外还需要在配置文件中添加一个配置节点
所以这个注解就是在开启spring配置类自动扫描@component注解的功能,@SpringBootConfiguration+@ComponentScan两个注解都被注解@SpringBootApplication继承,注解@SpringBootApplication又用于标注入口程序类,这就更加证实了我们的入口程序类就是一个spring配置类,是spring容器的一个组件 - @EnableAutoConfiguration【springBoot实现自动装配的核心】
- 这个注解的定义上使用了两个核心的第3方注解
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class)
- @AutoConfigurationPackage:自动配置包
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {
- Registrar.class导入的目的就是要实例化对象Registrar,因为Registrar.class 会将入口程序类 【即@SpringBootApplication标注的类】所在包及包下面所有子包里面的所有spring容器的组件扫描到Spring容器,这样在获取容器的时候容器中的内容就是针对这整个项目所需要的
- @Import(AutoConfigurationImportSelector.class)【整个springBoot自动装配的精华】
- 这个注解也是导入了一个类的class,目的一样是通过反射获取类的对象
- 见名知意,要实例化的这个类AutoConfigurationImportSelector意思就是"自动配置导入选择器"
- 问题:它要选择什么?
- 这个类有一个方法叫getAutoConfigurationEntry(),这个方法用于返回一个对象AutoConfigurationEntry,显然这个对象就是自动装配实体类
- 但是在获取这个实体类的时候需要使用到一个方法返回的List< String >集合数据,这个方法叫getCandidateConfigurations():获取候选配置
- 问题:候选的配置有哪些?
- 点进这个方法,可以发现它返回这个List< String >的时候依赖另一个方法loadFactoryNames(),这个方法需要两个参数getSpringFactoriesLoaderFactoryClass()的返回值+getBeanClassLoader()的返回值,getBeanClassLoader()返回的就是一个类加载器
- 再来看这个loadFactoryNames(),这个方法的返回值又依赖另一个方法loadSpringFactories(),并且它需要使用刚刚提到的getBeanClassLoader()返回的类加载器作为参数
- 重点来看这个loadSpringFactories()
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //定义一个map集合存储数据 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //通过传入的类加载器加载资源文件"META-INF/spring.factories" //我们都知道通过类加载器加载的都是这个类的class对象 //这里先用类加载器获取了资源文件按 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //判断资源文件中下一行还有没有数据 while(urls.hasMoreElements()) { //有数据将指针下移,这不就和JDBC扫描结果集一样? URL url = (URL)urls.nextElement(); //封装读取出来的数据 UrlResource resource = new UrlResource(url); //再将数据封装进Properties 对象中 Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); //下面不看了 while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
- 通过阅读上面的源码,可以发现这个方法做的事情就是读取资源文件,我们我们来看看这个资源文件是什么
- 看到这里我们可以设想为什么要读取这个配置文件中的类的全限定名?显然就是为了通过类的全限定名,调用Class.forName(“类的全限定名称”)来获取类的实例
- 为什么要获取这些类的实例?因为它们就是springBoot实现自动配置的配置类,只要获取到对应场景的自动配置类,那么这个场景需要的spring容器配置和其他一些相关的配置,这个场景的配置类都可以自动帮我们搞定
- 问题:这个文件中一共有127个自动配置类,对应了127个我们要使用的场景,读取配置文件的时候使用的是while循环,也就是说这个文件中的所有自动配置类都已经被读到了,那么会将这个127个自动装配类都实例化吗?
- 答案显然是不会,本来反射就会降低程序效率,你还每次启动项目都通过反射实例化127个对象?显然不成立,那么我们怎么挑选出我们当前场景需要的自动配置类呢?
- 答案就在每个自动配置类的定义中,我们可以随便点进去一个自动配置类看看
- 注解@ConditionalOnClass它继承了注解@Conditional,注解@Conditional的作用:在满足指定条件的时候才将某个 bean 加载到应用上下文中/配置文件中
- 而注解@ConditionalOnClass({ })的作用就是当项目中加载了{ }中所有指定的class文件的时候,就将这个 bean 加载到应用上下文中/配置文件中
- 那么我们是怎么加载了某一个自动配置类的@ConditionalOnClass({ })指定的所有的类的class文件的?这就要夸奖一下我们的maven小兄弟了,由于有了它,再也不用自己手动的导入jar包了,而class文件就是存放在导入的jar包里面的
- 还记得前面说的应用场景?一个应用场景对应了一个自动装配类;在讲启动器的时候我们也说了一个场景对应一款启动器,所以我们在某一个启动场景中就需要使用这个场景的启动器,启动器就会为我们导入这个场景中需要的jar包,jar包导入之后,这个场景下的自动配置类的实例化条件就会满足,一旦条件满足这个自动配置类的bean就会别加载到配置文件中/配置类中,那么这个场景下的自动配置对象就会被spring容器实例化,这样springBoot自动配置就实现了
- 注意:自动装配类≠启动器
- 打完收功
- 注意:Conditional是spring的底层注解,在spring中所有的注解@ConditionalOnXXX都继承了这个注解@Conditional,所以不止有@ConditionalOnClass这样一个将自动配置类注入spring容器中的条件,上面主要是只看到了@ConditionalOnClass注解,所以只讲了这个注解
- 这个注解的定义上使用了两个核心的第3方注解
4.结论
springboot将所有的使用场景划分出来,这个场景下的启动器划分出来,这个场景下要使用的自动装配类也定义出来,当用户使用的时候,只需要在pom.xml中导入对应场景的依赖,对应的自动配置类就会被加载到spring容器中进行生效;当出现多个场景场合的时候就将所有的场景的自动配置类融合成一个大的自动配置类再装入spring容器中,这就和原来使用xml文件的时候多个配置文件可以使用< import >合并为一个是一个道理,在纯Java配置spring的时候注解@import也是这个道理,所有的自动配置都是在项目启动的时候扫描并加载,所有的配置都被这个场景下的自动配置类为我们全权配置好了,我们只管使用即可
这就是springBoot自动配置的秘密
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作;
- 以前我们需要自己配置的东西,自动配置类都帮我们解决了
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中﹔
- 它会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件﹔
- 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
下面的不用看,只是在理解这个过程的时候写的一些自己的感悟流程
- 扫描的文件就是"META-INF/spring.factories"
- 所有的自动配置类的全限定名都写在了这个配置文件里面,在springBoot启动的时候他会去加载这个文件,并将其中的每一行/每一个类的全限定名作为一个properties对象封装到一个list集合里面,然后通过判断每一个类上的条件注解@ConditionalOnClass(XXX.class,…)来看我们的项目是不是已经导入了这个条件中包含的所有class/jar包/依赖,只要全部都导入了,证明我们的项目确实需要使用进行这个场景的的配置,那么这个场景对应的自动配置类就会被装配到spring容器中,有了这个场景的自动配置类,我们就可以简化原来在这个场景中使用SSM框架的繁琐配置步骤了,它都会去帮助我们将它配置好
- springBoot启动的时候去扫描"META-INF/spring.factories"文件,这个文件中存储了几乎所有场景中需要的自动配置类的全限定名,这里面的类的名称都有一个共同的后缀"XXXAutoConfiguration",即XXX场景下的自动配置类,看到Configuration是不是就能联想到注解@Configuration?
- 我们可以点进每一个自动配置类里看一眼,你可以很轻易的就发现每一个类上面都有一个注解@Configuration,这是不是就代表了这个类就是这个场景下的配置类?并且我们可以仔细的翻一翻这些类中的方法,有很多方法上面都使用了注解@Bean,这是不是就更加能够证实存在于"META-INF/spring.factories"文件中的一个XXXAutoConfiguration配置类,就是这个场景下对应的配置类了呢?并且这个配置类中已经为我们定义好了带有注解@Bean的方法,即已经为我们装配了必要的实体类了,当获取spring容器的时候将这个配置类传入,容器中的内容就有了,容器就会按照我们的配置类为我们装配实例对象,这就是springBoot自动装配的秘密(疑问:当项目中有一套环境以上的jar包被导入,那么spring容器怎么形成?解决方法及其就和前面学习spring的时候有多个配置文件的时候一样,可以使用import将多个合成一个,然后再将这个融合的拿去获取spring容器)
- 将对应环境的自动配置类作为参数传入就可以获取到对应的spring容器,自动配置就会生效,以前我们需要手动配置的东西现在springBoot都通过当前环境下的自动配置类帮我们配置好了
- 整个JavaEE需要的动配置依赖需要使用的jar包都在导入的的依赖:spring-boot-autoconfigure-2.3.4.RELEASE.jar/org/springframework/boot/autoconfigure包下