Spring @Conditional注解

背景

团队所负责的一个项目由于功能的不断累加目前变的十分庞大,打算将其拆分成若干个单独的service,在这之中有一些共用的bean(例如interceptor, datasource等)需要移到一个公共的module中。但是这个公共的module已经被其他service依赖,为了避免对其他service产生未知的影响,需要对bean的加载进行一些限制做到按需加载,为此使用Spring提供的@Conditional注解进行控制。

@ConditionalOnProperty

该注解会根据指定的配置,判断是否加载对应的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。

@ConditionalOnResource

该注解在指定配置文件存在的情况下加载bean

@Configuration
@ConditionalOnResource(resources = "classpath:conf/apiserver.properties")
public class ApiServerConfigurationFacade {
}

需要注意的是仅在classPath下的配置文件才会有效,外部配置文件无法检测

@ConditionalOnClass

只有在classpath中存在指定class是才会加载

@Configuration(proxyBeanMethods = false)
@Import({ NoOpMeterRegistryConfiguration.class, CompositeMeterRegistryConfiguration.class })
@ConditionalOnClass(CompositeMeterRegistry.class)
public class CompositeMeterRegistryAutoConfiguration {

}

@ConditionalOnExpression

在满足 SpEL表达式的情况下加载bean

 @ConditionalOnExpression( "'${catalina.home}' != '.' ")
  public static class Tomcat {
    }

SpEL表达式在此不做过多介绍,${}中为可以在environment中取到的变量,表达式语法与java类似

@ConditionalOnMissingBean

在指定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

如果我们去查看以上注解的源码,可以发现他们都依赖于@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
  • SpringBootCondition

AbstractNestedCondition

AbstractNestedCondition可以用来组合多个condition,spring提供了三个基类

  • AnyNestedCondition
  • NoneNestedConditions
  • AllNestedConditions

AnyNestedCondition

满足其中任意一个条件都会加载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 {

		}

	}

NoneNestedConditions

满足其中任意一个条件都会不会加载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 {

		}

	}

}

AllNestedConditions

满足所有条件才会加载bean.

SpringBootCondition

上述提到的几个@Conditional注解都是基本SpringBootCondition来实现的,我们可以直接参考spring的源码来实现自己的需求。
也可以参考这位的实现https://blog.csdn.net/wtopps/article/details/84110904

你可能感兴趣的:(springboot,spring,java,后端)