TLDR:
org.springframework.boot.autoconfigure.AutoConfiguration.imports,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,spring-boot-autoconfigure,@AutoConfiguration
Preface
@Conditional 是Spring4新提供的注解
和它类似的有 @DependsOn(“beanName”) 注解, 组件依赖注解, 也就是被依赖的组件会比自身先注册到IOC容器中。
@Conditional
@Conditional({Class, Class}) 的值为 一组 Condition接口 的实现类
- 是否满足 Condition#matches 方法 (返回true/false)
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
值得注意的是, Spring本身也有很多实现类 (e.g. AllNestedConditions、AnyNestedCondition、NoneNestedConditions) 和扩展 (e.g. @ConditionalOnXxx)
XxxNestedConditions类 系列注解
这些类本身就是一个 Condition 类, 可直接作为注解 @Conditonal 的条件 (基础条件), 可用于创建复合条件 (目的).
复合条件使用静态内部类的方式实现, 条件可以是 @Conditional 也可以是 @ConditionalOnXxx;
- AllNestedConditions 全部匹配
- AnyNestedCondition 任意匹配
- NoneNestedConditions 全不匹配
e.g.
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
static class Beans {
}
}
@ConditionalOnXxx 系列注解
- @ConditionalOnBean
- @ConditionalOnSingleCandidate
- @ConditionalOnMissingBean
Spring 对 @Condition 做了拓展,提供了 Condition 接口的通用实现 (e.g. OnBeanCondition 类),并包装成对应的类似 @ConditonalOnBean @ConditonalOnProperty 等注解
- @ConditionalOnProperty
配置文件 是否有对应值( havingValue, @ConditionalOnProperty 是通过 havingValue 与配置文件中的值进行判断来实现)
@ConditionalOnProperty(prefix = "spring.redis",name = "enabled", havingValue = "true")
可结合 @ConfigurationProperties(prefix=“spring.redis”) 来装配参数
@ConditionalOnXx 底层实现
- SpringBootCondition抽象类
public abstract class SpringBootCondition implements Condition {
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// matches 方法
// 通过 元数据信息 解析对应的 类、方法 名
String classOrMethodName = getClassOrMethodName(metadata);
try {
/**
* getMatchOutcome 获取对应的 ConditionOutcome, 由子类实现
* ConditionOutcome 类封装了条件的匹配结果和匹配信息
*/
ConditionOutcome outcome = getMatchOutcome(context, metadata);
// 匹配信息打印
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
// 返回是否匹配
return outcome.isMatch();
} catch (NoClassDefFoundError ex) {
// ...
}
}
}
SpringBootCondition 是个抽象类,实现了 Condition 接口,核心方法 match 的实现委托给了 getMatchOutcome 方法实现,其具体实现自然是由 Spring Boot 提供的各种条件类复写, 其返回的 ConditionOutcome 对象是对条件匹配结果和匹配信息的封装.
以下类仅关注
getMatchOutcome方法实现, 他们都是SpringBootCondition子类
- OnBeanCondition 类
- OnPropertyCondition 类
ConditionalOnProperty
注意: ConditionalOnProperty 配置是从容器中获取的, 而不是在配置文件中获取, 有些时候配置文件没写, 依然存在于容器中, 这时 Condition 依然会判定为不加载
@Autowired Environment env;
@Bean
public ApplicationRunner runner() {
return r -> {
Map<String, Object> map = new HashMap();
for (Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator();
it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof MapPropertySource) {
map.putAll(((MapPropertySource) propertySource).getSource());
}
}
};
}
@Import 和 @ImportResource 注解
@Import
将外部类注入到spring容器中. 在4.2之前只支持导入配置类, 在4.2之后,@Import注解支持导入普通的 java类,并将其声明成一个bean.
和@Bean的区别是:
-
@Import 的 id 为全类名, @Bean的 id 是方法名
-
@Import 更简单, 相比于@Bean 使用无参构造器直接 new() 更简单;
-
能使用 ImportSelector, 批量导入
public class MyImportSeletor implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.bidu.A","com.bidu.B"};
}
}
@ImportSelector
- 获取注解上的参数
- 我们给 @EnableA 注解添加一个参数 enabled,当 enabled = true 时导入
AConfiguration.class,当 enabled = false时不导入任何类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AConfiguration.class)
public @interface EnableA {
boolean enabled() default true;
}
- 接着我们实现一个
ImportSelector.class
public class AImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
Map<String, Object> attributes = metadata
.getAnnotationAttributes(EnableA.class.getName());
boolean enabled = (boolean) attributes.get("enabled");
if (enabled) {
return new String[]{AConfiguration.class.getName()};
} else {
return new String[]{};
}
}
}
关键两步:
metadata.getAnnotationAttributes(EnableA.class.getName())获取注解信息, 得到一个 Mapreturn new String[] {}返回要加载的类的全限定名
-
我们可以通过 ImportSelector 中提供给我们的 AnnotationMetadata 来获得 EnableA 中的属性enabled
-
当 enabled 为 true 时,我们返回
AConfiguration.class的全限定名;当 enabled 为 false 时,返回空数组即可 -
最后我们将 ImportSelector 子类写到
@Import(AImportSelector.class)就行了
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AImportSelector.class)
public @interface EnableA {
boolean enabled() default true;
}
- 当我们将enabled设置为false时,就不会配置AConfiguration.class了
@EnableA(enabled = false)
@SpringBootApplication
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
@ImportBeanDefinitionRegistrar
还有另一种方式也可以拿到注解的属性,那就是 ImportBeanDefinitionRegistrar
和 ImportSelector 不同的是,ImportBeanDefinitionRegistrar 可以直接注册 BeanDefinition
如果我们用 ImportBeanDefinitionRegistrar 来实现上面的功能大概就是这个样子
public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> attributes = metadata
.getAnnotationAttributes(EnableA.class.getName());
boolean enabled = (boolean) attributes.get("enabled");
if (enabled) {
registry.registerBeanDefinition("a", new RootBeanDefinition(A.class));
}
}
}
关键一步:
registry.registerBeanDefinition("beanName", new RootBeanDefinition(A.class))
然后同样的把 @Import(AConfiguration.class) 改为 @Import(AImportBeanDefinitionRegistrar.class) 就行了
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AImportBeanDefinitionRegistrar.class)
public @interface EnableA {
boolean enabled() default true;
}
@ImportResource
导入 xml 一类的配置文件:
@ImportResource(locations="classpath:applicationContext.xml")
自动装配: spring.factories
用法
一般在一个项目中也会有一些标记了@Configuration 的配置类, 只要Spring能够扫描到这个类,A实例就能被注入.
如果这个配置类是写在同一个包下,那么 Spring 默认的扫描路径就能扫到, 但如果做成一个 Starter,对应的包名可能就扫不到了, 所以我们需要用另外的方式来导入这个配置类: spring.factories 或 自定义注解 @EnableXXX
使用 spring.factories 来导入配置(注解和spring.factories选择一种就可以啦)
我们需要在resources目录下新建一个META-INF目录,然后在META-INF目录下创建spring.factories文件
接着我们需要在spring.factories中将AConfiguration.class配置上去
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.xxx.AConfiguration
一般情况下,如果是配置在spring.factories中的配置类都会取名xxxAutoConfiguration,所以我们在这里修改名称为AAutoConfiguration
最后在spring.factories中的配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.xxx.AAutoConfiguration,\
com.xxx.xxx.BAutoConfiguration
这样当你的项目启动后,Spring就会自动读取spring.factories将AAutoConfiguration(AConfiguration)扫描进去了
Spring boot 2.7.X 之后的新版配置: AutoConfiguration.imports
- 新建文件夹
META-INF/spring - 新建文件
org.springframework.boot.autoconfigure.AutoConfiguration.imports - 文件内写入: com.xxx.xxx.AAutoConfiguration, 多个可换行 (之前是用逗号, 现在是直接换行)
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
Also see:
org.springframework.boot.context.annotation.ImportCandidates#LOCATION
配置替换 AutoConfiguration.replacements
- org.springframework.boot.autoconfigure.AutoConfiguration.replacements
仅针对 afterName/beforeName/excludeName
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.howeres.config.AAConfiguration
com.howeres.config.AAConfiguration$CCConfiguration
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements
com.howeres.config.BBConfiguration=\
com.howeres.config.CCConfiguration
Also see:
org.springframework.boot.autoconfigure.AutoConfigurationReplacements#LOCATION
import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(afterName = "com.howeres.config.BBConfiguration")
// @AutoConfiguration
public class AAConfiguration {
public AAConfiguration() {
System.err.println("AAConfiguration");
}
@Bean
public String newBB() {
System.err.println("AA");
return "AA";
}
@AutoConfiguration
static
class CCConfiguration {
public CCConfiguration() {
try { TimeUnit.SECONDS.sleep(2);} catch (InterruptedException ignore) {}
System.err.println("CCConfiguration");
}
@Bean
public String newCC() {
System.err.println("CC");
return "CC";
}
}
}
扩展阅读
https://reflectoring.io/spring-boot-conditionals
https://docs.spring.io/spring-boot/reference/features/developing-auto-configuration.html
本文深入探讨了Spring框架中的条件注解,如@Conditional和@ConditionalOnXxx,以及它们如何控制组件的装配。@Conditional注解允许基于特定条件决定bean是否注册到IOC容器。@ConditionalOnBean、@ConditionalOnSingleCandidate和@ConditionalOnMissingBean则提供了对bean存在的条件判断。此外,还介绍了SpringBootCondition抽象类及其在@ConditionalOnProperty中的应用,用于根据配置属性决定bean的启用。@Import和@ImportResource注解则用于导入类和XML配置。文章详细阐述了这些注解的工作原理和使用场景,并给出了示例代码。
5840

被折叠的 条评论
为什么被折叠?



