这个主要是解析springboot中的自动配置的原理是什么,以及为什么要有这个自动配置,从这两个方面进行解析,看七月老师如何来进行讲解这个自动配置
一、@SpringBootApplication注解的理解
@SpringBootApplication这个注解是启动类上的注解,在这个注解之中存在着许多玄机!在启动的时候,springboot如何将第三方的配置类加入到spring IOC容器中,那就是通过这个注解了,详细看一下这个注解的内部是什么样子的?
@SpringBootApplication注解主要是由三个注解组成,分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan,@ComponentScan这个注解主要是扫描包,将带有@Component注解的类扫描出来,这个就简单的进行说明一下,重点来看一下@SpringBootConfiguration和@EnableAutoConfiguration这两个注解,其中最核心的注解是@EnableAutoConfiguration,这个主要是负责加载很多的第三方的SDK
1、@SpringBootConfiguration注解
这个注解其实看一下源码的话,其实就是一个@Configuration注解的封装
2、@EnableAutoConfiguration注解(springboot自动配置最核心的机制)
这个注解其实是跟@Configuration注解做的事情是类似的,目的也是将需要加载的bean配置好,然后再把配置好的bean放入到spring IOC容器中,只不过这其中的过程可能复杂一些,本质其实就是在做一个配置类的事情
注意点:@EnableAutoConfiguration注解其实是你所引用的第三方的SDK,举例:maven中pom.xml文件中引入的第三方的包就是需要使用这个注解进行注入到spring IOC容器中的
看一下@Import导入的那个AutoConfigurationImportSelector类,这个类中有一个核心的方法:
1 @Override 2 public String[] selectImports(AnnotationMetadata annotationMetadata) { 3 if (!isEnabled(annotationMetadata)) { 4 return NO_IMPORTS; 5 } 6 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader 7 .loadMetadata(this.beanClassLoader); 8 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, 9 annotationMetadata); 10 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 11 }
这个方法研究了一下,很复杂,经过采纳七月老师的讲解,联合自己研究代码,这个类的主要作用就是加载第三方的配置类,具体到哪一个文件的话,可以随便找一个第三方的SDK,比如springboot中的autoconfigure这个jar包,他会加载spring.factories这个文件中的key-value键值对,其实就是加载配置的一些第三方全路径的类。加入到spring IOC容器中
二、@EnableAutoConfiguration注解模块装配机制
这里并不是就是指指这一个特定的注解,而是以Enable开头的一类注解的模块装配机制,这个机制是这一类注解的通用原理,自己写一个启动类,来验证一下这个模块装配机制
1、创建启动类LOLApplication
当加上@ComponentScan注解之后,这个启动类是可以正常启动的,因为@ComponentScan注解是可以扫描包下的文件,只要有@Configuration+@Bean注解都会注入到spring IOC容器中的,所以这个配置类中的实现类是注入成功的
1 @ComponentScan 2 public class LOLApplication { 3 public static void main(String[] args){ 4 ConfigurableApplicationContext context = new SpringApplicationBuilder(LOLApplication.class) 5 .run(args); 6 // 验证是否加入到spring IOC容器中 7 ISkill iSkill = (ISkill) context.getBean("irelia"); 8 iSkill.r(); 9 } 10 }
2、之前的配置类代码
我们只注入一个实现类,避免麻烦,来简化这个验证机制
1 @Configuration 2 public class HeroConfiguration { 3 4 @Bean 5 public ISkill irelia (){ 6 return new Irelia(); 7 } 8 }
3、@Import注解的两种用法
我们使用@Import注解来实现配置类注入成功,这里有两种用法:
(1)直接导入配置类的方式
1 @Import(HeroConfiguration.class) 2 public class LOLApplication { 3 public static void main(String[] args){ 4 ConfigurableApplicationContext context = new SpringApplicationBuilder(LOLApplication.class) 5 .web(WebApplicationType.NONE) 6 .run(args); 7 // 验证是否加入到spring IOC容器中 8 ISkill iSkill = (ISkill) context.getBean("irelia"); 9 iSkill.r(); 10 } 11 }
注意:这个需要使用SpringApplicationBuilder类的web方法,不启动web服务器,才能成功启动
(2)使用selector来导入
首先,需要新建LOLConfigurationSelector类
1 public class LOLConfigurationSelector implements ImportSelector { 2 3 @Override 4 public String[] selectImports(AnnotationMetadata annotationMetadata) { 5 return new String[] {HeroConfiguration.class.getName()}; 6 } 7 }
然后,在@Import注解中导入这个selector类
1 @Import(LOLConfigurationSelector.class) 2 public class LOLApplication { 3 public static void main(String[] args){ 4 ConfigurableApplicationContext context = new SpringApplicationBuilder(LOLApplication.class) 5 .web(WebApplicationType.NONE) 6 .run(args); 7 // 验证是否加入到spring IOC容器中 8 ISkill iSkill = (ISkill) context.getBean("irelia"); 9 iSkill.r(); 10 } 11 }
4、创建自定义的Enable注解
我们通过创建这个@EnableLOLConfiguration注解,来实现这个模块装配的注解,看一下注解的实现代码:
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.TYPE) 3 @Documented 4 @Import(LOLConfigurationSelector.class) 5 public @interface EnableLOLConfiguration { 6 7 }
看一下这个注解的使用,其实就跟其他注解一样,只是在类上加上这个注解就好
1 @EnableLOLConfiguration 2 public class LOLApplication { 3 public static void main(String[] args){ 4 ConfigurableApplicationContext context = new SpringApplicationBuilder(LOLApplication.class) 5 .web(WebApplicationType.NONE) 6 .run(args); 7 // 验证是否加入到spring IOC容器中 8 ISkill iSkill = (ISkill) context.getBean("irelia"); 9 iSkill.r(); 10 } 11 }
这样的话,自定义的模块装配注解也实现了,让我们对比一下springboot中与我们自定义的模块装配注解有什么关联,有什么区别!
三、对比总结
1、对比记忆
总体的思路是一致的,都是通过@Import注解导入配置类的全路径名,从而将配置类实例化,进而注入到spring IOC容器中,方便我们使用,对比一下selector类实现的ImportSelector接口的selectImports方法,其实总体是一样的:
## 自定义的LOLConfigurationSelector类代码:
1 public class LOLConfigurationSelector implements ImportSelector { 2 3 @Override 4 public String[] selectImports(AnnotationMetadata annotationMetadata) { 5 return new String[] {HeroConfiguration.class.getName()}; 6 } 7 }
## springboot中@EnableAutoConfiguration注解中使用的@Import注解导入的AutoConfigurationImportSelector类代码(只看实现的selectImports方法):
1 public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, 2 ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { 3 4 @Override 5 public String[] selectImports(AnnotationMetadata annotationMetadata) { 6 if (!isEnabled(annotationMetadata)) { 7 return NO_IMPORTS; 8 } 9 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader 10 .loadMetadata(this.beanClassLoader); 11 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, 12 annotationMetadata); 13 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 14 } 15 }
看源码,这个getAutoConfigurationEntry中实现,会找到getCandidateConfigurations方法,最终会看到SpringFactoriesLoader类的loadFactoryNames这个方法,这个就是提取spring.factories中的需要加载注入spring IOC容器的配置类的全路径名的信息
2、总结
这个模块装配注解用到的是Java中的SPI 机制,SPI的全名为Service Provider Interface,这种机制就是为了去应对系统中的变化,将这种变化做了隔离,关系依赖大概就是这个样子的:
等同于 基于interface接口 + 策略模式 + 变化点(配置文件中)
内容出处:七月老师《从Java后端到全栈》视频课程