自动配置注解的逻辑: 在启动类的基础上,导入了springboot的大量自动配置类,以至于自己不用关心配置实现过程(约定大于配置)
大量自动配置类是如何导入的?
Spring提供了一个SpringFactories功能(SPI: service provider interface ),读取固定文件META-INF/spring.factories,按照格式配置的内容会被加载,对应EnableAutConfiguration就是springboot要使用的自动配置类.
交给一个导入的Selector,通过类的全路径名称,实现配置类的加载,通过条件的判断,筛选满足的配置类.
所有的自动配置类都是springboot完成的? 不是,有第三方扩展的
mybatis-spring-boot-starter: mybatis-spring-boot-autoconfigure
dubbo-spring-boot-starter: dubbo-spring-boot-autoconfigure
knife4j-spring-boot-starter:knife4j-spring-boot-autoconfigure
所有的自动配置类都需要加载? 不是,加载不加载取决于条件注解是否满足
@ConditionalOnClass
@ConditionalOnMissingClass
@ConditionalOnBean
@ConditionalOnMissingBean
@ConditionalOnProperty 注解属性
prefix 前缀
name/value 属性名
havingValue 是否有值并且相等
matchIfMissing 如果没有这个属性,是否匹配(如果matchIfMissing 值为true,则表示没有)
spring.factories作用:
————————————————
在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。该文件的格式为键值对,键是自动配置类的全限定名,值是该自动配置类所对应的配置类的全限定名。
一、主要的注解:
@SpringBootApplication是springBoot最核心的一个组合注解 , 其组合了如下注解:
SpringApplicationConfiguration: 包装了一个@Configuration的注解,所以有这个注解的类本身就是一个配置类(启动类是个配置).
ComponentScan: 扫描 自定义的各种注解所在的包,比如service controller 全局异常捕获(加载和扫描我们自己的业务逻辑).
EnableAutoConfiguration: 开启自动配置加载(自动加载配置文件)
二、启动步骤:
2.1.导入选择器:
-
@SpringBootApplication注解上标注的@EnableAutoConfiguration用Import导入了一个选择器类AutoConfigurationImportSelector
@Import(AutoConfigurationImportSelector.class)
2.2.执行selectImports方法:
执行选择器的selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
方法返回结果是一个数组String[] ,元素是自动配置类全路径名称.
然后就会导入这些配置类(只导入符合条件的配置类<会用@Condition注解设置条件>)(这些配置类叫做自动配置类)。
问题:如此多的配置类,不可能在一个项目中全部使用.
要进行过滤和筛选,哪些有效,哪些无效.
3.4.3 条件注解@Conditional选择
在众多的自动配置类中,就需要满足一定条件,才加载此配置类.(不满足条件,配置类不加载的.)
这个功能是spring提供的条件注解,在springboot做了衍生.
一个springboot工程启动,如果观察哪些配置满足条件,可以使用debug模式,对应root范围.
会在日志中出现提示: matched configuration或unmatched configuration, 提示哪些条件没有满足.
下边是一些衍生注解:
@ConditionalOnClass: 标注在类上的注解和标注在方法上的注解,标注在类上 条件满足(内存中有满足条件的类)类加载, 标注在方法上条件满足方法加载.不满足,则不加载.
指定class必须在依赖环境中存在.存在则满足,不存在则不满足
@ConditionalOnMissingClass:与上面的条件逻辑是相反的.存在则不满足,不存在则满足
@ConditionalOnBean:类注解和方法注解, 某个限定条件的bean对象在容器中存在 则满足,不存在则不满足
@ConditionalOnMissingBean:与上面条件逻辑相反.一般是留给自定义扩展的
@ConditionalOnProperty:标注在类上的注解和标注在方法上的注解,标注在类上 条件满足(内存中有满足条件的属性<变量>)就加载此配置类,否则不加载
注意: 这些条件注解可以添加在配置类上也可以加在方法上(条件满足执行方法)
测试案例: 底下是一个用来测试@ConditionalOnProperty和@ConditionalOnMissingClass的案例
1.配置了@ConditionalOnProperty的配置类:
package cn.zyq.stater.config2;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@Configuration
//ConditionalOnProperty 描述: test4.username=root
// 属性有test4.password=root 的属性数据 条件则满足,没有,或者值不是123456都不满足
//- 使用入口配置类扫描这个测试
//matchIfMissing表示如果条件不满足要不要执行(如果matchIfMissing = true表示test4.password=root 不存在也表示Condition满足)
@ConditionalOnProperty(prefix = "test4", value = "password" ,havingValue = "root" ,matchIfMissing = true)
public class Config6_OnCondition {
public Config6_OnCondition(){
System.out.println("条件配置类Config6_OnCondition ,条件满足, 加载此类");
}
}
2.扫描此配置类的Config6_OnCondition2:
package cn.zyq.stater.config2;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan(basePackages= {"cn.zyq.stater.config2"})//扫描Config6_OnCondition并创建此配置类的对象
@PropertySource(value = "classpath:jdbc.properties",ignoreResourceNotFound = false)
public class Config6_OnCondition2 {
}
3. jdbc.properties:
test4.username=root
test4.password=root
4.测试类:
package cn.zyq.stater;
import cn.zyq.stater.config2.Config6_OnCondition2;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test6 {
@Test
public void test6(){
AnnotationConfigApplicationContext ac3=new AnnotationConfigApplicationContext(Config6_OnCondition2.class);
}
}
3.4.4.导入的众多配置类位置:
通过selector导入String[]数组中的配置类, 在哪里? (springboot如果将其写死.没有扩展的空间了).
springboot提供了自动配置的导入逻辑,需要准备一个META-INF/spring.factories的文件,这个文件的格式,可以参考spring-boot-autoconfigure中的内容.
加载自动配置的逻辑全部介绍完了.
总结:
-
启动类的核心注解(EnableAutoConfiguration)
-
叫Enable的注解都在导入
-
Import注解导入一个selector选择器(返回一堆配置类的全路径名称String[]) 读取META-INF/spring.factories的文件中的key和value
@Import(AutoConfigurationImportSelector.class)
注意: 并不是所有String[]中的自动配置类都加载,需要满足条件注解
三、自动配置案例:
3.1.META-INF/spring.factories
对应代码:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.zyq.stater.config3.autoconfig.ZyqAutoConfigure
3.2.自动配置类:
对应代码:
package cn.zyq.stater.config3.autoconfig;
import cn.zyq.stater.bean.User1;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration//本类作为配置类,并且在spring.factories中进行配置了,如果本项目作为jar引入到其他项目中了
//即使不启动本项目, 引入本jar的项目启动时会根据spring.factories加载本配置类.然后根据 @ConditionalOnProperty判断是否会执行createUser1方法
@PropertySource(value = "jdbc.properties", ignoreResourceNotFound=false)
@Slf4j
public class ZyqAutoConfigure {
//本类ZyqAutoConfigure作为配置类是肯定会被加载到内存的,但本方法createUser1()在加载时是否会执行要看@ConditionalOnProperty条件是否成立
@Bean
//首先要加载了本配置类, 然后还要条件满足, 就会创建Bean对象(User1类型),(条件不满足,user对象不创建<需要用到jdbc.properties中的配置信息>)
@ConditionalOnProperty(prefix = "zyq", value = "enable", havingValue = "true" , matchIfMissing = false)
public User1 createUser1(){
log.info("条件满足1,createUser1()创建User1对象");
System.err.println("条件满足2,createUser1()创建User1对象");
return new User1();
}
}
项目主启动类运行时就会自动加载spring.factories , 然后就会加载ZyqAutoConfigure配置类,然后会根据条件执行createUser1()方法。