团队所负责的一个项目由于功能的不断累加目前变的十分庞大,打算将其拆分成若干个单独的service,在这之中有一些共用的bean(例如interceptor, datasource等)需要移到一个公共的module中。但是这个公共的module已经被其他service依赖,为了避免对其他service产生未知的影响,需要对bean的加载进行一些限制做到按需加载,为此使用Spring提供的@Conditional
注解进行控制。
该注解会根据指定的配置,判断是否加载对应的bean
@Bean
@ConditionalOnProperty(prefix = "spring", name = "example.values",havingValue="true")
class ExampleAutoConfiguration {
}
在这个例子中,如果存在spring.example.values=true
时,会加载ExampleAutoConfiguration。除了prefix
,name
,havingValue
之外,还有一个matchIfMissing
属性,该属性表明如果指定的配置不存在,是否加载bean,默认为false。如果有一个bean希望默认情况下都加载,特定值不加载,可以设为true。
该注解在指定配置文件存在的情况下加载bean
@Configuration
@ConditionalOnResource(resources = "classpath:conf/apiserver.properties")
public class ApiServerConfigurationFacade {
}
需要注意的是仅在classPath下的配置文件才会有效,外部配置文件无法检测
只有在classpath中存在指定class是才会加载
@Configuration(proxyBeanMethods = false)
@Import({ NoOpMeterRegistryConfiguration.class, CompositeMeterRegistryConfiguration.class })
@ConditionalOnClass(CompositeMeterRegistry.class)
public class CompositeMeterRegistryAutoConfiguration {
}
在满足 SpEL
表达式的情况下加载bean
@ConditionalOnExpression( "'${catalina.home}' != '.' ")
public static class Tomcat {
}
SpEL
表达式在此不做过多介绍,${}
中为可以在environment中取到的变量,表达式语法与java类似
在指定bean不存的情况下加载bean
@Bean
@ConditionalOnMissingBean(name = "kafkaListenerContainerFactory")
ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ObjectProvider<ConsumerFactory<Object, Object>> kafkaConsumerFactory) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory
.getIfAvailable(() -> new DefaultKafkaConsumerFactory<>(this.properties.buildConsumerProperties())));
return factory;
}
该注解主要用在公共模块中,通过该注解可以为其他下游模块提供默认的bean,当下游模块决定自行提供bean时不会导致bean confilct。
如果我们去查看以上注解的源码,可以发现他们都依赖于@Conditional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition} classes that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
该注解可以申明一个Condition
数组,我们可以通过实现Condition
来满足自己的需求。我们有如下两种方式来实现Condition
AbstractNestedCondition可以用来组合多个condition,spring提供了三个基类
满足其中任意一个条件都会加载bean,看一个例子
static class DefaultCookieSerializerCondition extends AnyNestedCondition {
DefaultCookieSerializerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnMissingBean({ HttpSessionIdResolver.class, CookieSerializer.class })
static class NoComponentsAvailable {
}
@ConditionalOnBean(CookieHttpSessionIdResolver.class)
@ConditionalOnMissingBean(CookieSerializer.class)
static class CookieHttpSessionIdResolverAvailable {
}
}
满足其中任意一个条件都会不会加载bean
@Configuration(proxyBeanMethods = false)
@Conditional(MultipleNonPrimaryMeterRegistriesCondition.class)
class CompositeMeterRegistryConfiguration {
@Bean
@Primary
AutoConfiguredCompositeMeterRegistry compositeMeterRegistry(Clock clock, List<MeterRegistry> registries) {
return new AutoConfiguredCompositeMeterRegistry(clock, registries);
}
static class MultipleNonPrimaryMeterRegistriesCondition extends NoneNestedConditions {
MultipleNonPrimaryMeterRegistriesCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnMissingBean(MeterRegistry.class)
static class NoMeterRegistryCondition {
}
@ConditionalOnSingleCandidate(MeterRegistry.class)
static class SingleInjectableMeterRegistry {
}
}
}
满足所有条件才会加载bean.
上述提到的几个@Conditional注解都是基本SpringBootCondition来实现的,我们可以直接参考spring的源码来实现自己的需求。
也可以参考这位的实现https://blog.csdn.net/wtopps/article/details/84110904