SpringBoot自动配置
0 引言
springboot的自动配置类直观的表现就是:通过一系列的注解,使得springboot项目在启动的时候从配置文件中加载需要自动配置的类,如果该配置类有引入相关的jar的文件的时候,springboot便会让对应的类实例化,注入容器中。即做到在没有任何配置的情况下就可直接使用。当没有引入对应的jar文件的时候springboot便不会自动实例化配置类。
1 原理
要想理解其中的原因核心便是围绕一个注解@SpringBootApplication
@SpringBootApplication注解
直接上源码:
@Target(ElementType.TYPE)//表示@SpringBootApplication的使用位置,表示用在类上
//1.CONSTRUCTOR:用于描述构造器
//2.FIELD:用于描述域
//3.LOCAL_VARIABLE:用于描述局部变量
//4.METHOD:用于描述方法
//5.PACKAGE:用于描述包
//6.PARAMETER:用于描述参数
//7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME)//表示@SpringBootApplication的存在阶段。
//1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
//2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将 会忽略
//3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
@Documented// “文档” 注解表明这个注解应该被 javadoc工具记录
@Inherited//“继承” 修饰@SpringBootApplication后,那么@SpringBootApplication用在哪个类上,且该类有子类的话,子类是可以继承父类中的@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
可以看到其是一个复合注解前面的几个是元注解并不是核心自动配置的原因重点在@SpringBootConfiguration
,@EnableAutoConfiguration
,@ComponentScan
这三个注解。
@SpringBootConfiguration注解
源码:
@Configuration
public @interface SpringBootConfiguration {...}
再一层
@Configuration注解
@Component
public @interface Configuration {...}
可以看到本质是个@Component
,这个注解很熟悉了吧,再SSM阶段直接在spring的xml配置<context:component-scan /> 或者 @ComponentScan
,容器启动的时候会扫描所有含有@Component
的类,通过反射机制将类的信息加载到内存中给IOC使用。
补充
@Configuration
的使用1.
@Configuration
告诉springboot其修饰的类是一个配置类
@Configuration
和@Bean
联合使用其等价<bean id='xxx' class='xxx.xxx'>。。。</bean>
@Configuration
public class Configuration {
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
// 方法名默认成为该bean定义的id
@Bean
public UserBean user() {
return new UserBean();
}
}
故再退一步@SpringBootConfiguration
注解会标注一个类是javaConfig,在spring或者springboot启动的时候会被扫描。可以根据@Bean注解实例化对象,作用等于在spring的xml文中定义一个< bean > < /bean> 。
@EnableAutoConfiguration注解
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
@Import(EnableAutoConfigurationImportSelector.class)
可以看到其使用了@Import
注解:本质是为了引入类EnableAutoConfigurationImportSelector
类,进入其父类AutoConfigurationImportSelector
的selectImports()
方法,核心代码如下:
// 从配置文件"META-INF/spring-autoconfigure-metadata.properties"中加载 AutoConfigurationMetadata
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//从注解中加载exclude和excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//从所有jar包下的/META-INF/spring.factories的文件中获取到候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//重中之重
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
关于META-INF/spring.factories
其再spring-boot-autoconfigure-1.5.9.RELEASE.jar
下
@AutoConfigurationPackage注解
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
@Import(AutoConfigurationPackages.Registrar.class)
它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;我们实际上将我们自己的包下的组件注入容器依赖的还是@AutoConfigurationPackage
注解
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//ioc
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
去调用DefaultListableBeanFactory.java
里的registerBeanDefinition()
方法
// Cannot modify startup-time collection elements anymore (for stable iteration)
//无法再修改启动时集合元素(用于稳定的迭代)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
。。。。。
}
将已经创建好的Bean对象注入beanDefinitionMap中
@ComponentScan注解
是暂时将这些符合类型的组件先排除。
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
basePackageClasses:指定具体扫描的类
includeFilters:指定满足Filter条件的类
excludeFilters:指定排除Filter条件的类
注意:
@ComponentScan
是可以指定包的扫描, 但是在@SpringBootApplication
这个注解里的@ComponentScan
是用来排除主键的。
3 原理:引入相关的jar的文件的时候,springboot便会让对应的类实例化
@Conditional注解
条件装配:满足Conditional指定的条件,则进行组件注入
@ConditionalOnBean()
当其内配置的Bean已经在容器里的时候其修饰的类才会被加载。如何和@Bean
合用只有IOC容器里有指定的类的时候@Bean
修饰的方法所创建的实例才会被注入IOC容器。
@ConditionalOnMissingBean()
仅当指定的Bean类和/或名称尚未包含在BeanFactory中时,此条件才匹配。如何和@Bean
合用只有ioc容器里没有指定的类的时候@Bean
修饰的方法所创建的实例才会被注入IOC容器。
@ConditionalOnMissingClass()
仅当指定的类不在类路径上时才匹配的条件。
@ConditionalOnWebApplication()
仅当应用程序上下文是Web应用程序上下文时才匹配的条件。
参考:https://blog.csdn.net/weixin_43142697/article/details/82889568