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
这样当你的项目启动后,Spring就会自动读取spring.factories将AAutoConfiguration(AConfiguration)扫描进去了
Spring boot 2.7.X 之后的新版配置为:
- 新建文件夹
META-INF/spring
- 新建文件
org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 文件内写入: com.xxx.xxx.AAutoConfiguration, 多个可换行
扩展阅读
https://reflectoring.io/spring-boot-conditionals/