目录
- 前言
- 注解详解
- @ConditionalOnBean 和 @ConditionalOnMissingBean
- @ConditionalOnClass 和 @ConditionalOnMissingClass
- @ConditionalOnCloudPlatform
- @ConditionalOnExpression
- @ConditionalOnJava
- @ConditionalOnJndi
- @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication
- @ConditionalOnProperty
- @ConditionalOnResource
- @ConditionalOnSingleCandidate
- @ConditionalOnWarDeployment
- 测试源码地址
前言
最近在写自己的spring-boot-starter,发现了几个小问题,如何判断外部引用的类,怎么通过一些限定条件决定这个类或者这个方法是否加载,是否交给spring的ioc去管理。在Spring 4.0 时代,我们可以通过 @Conditional 注解来实现这类操作。Spring Boot 在 @Conditional 注解的基础上进行了细化,无需出示复杂的介绍信 (实现 Condition 接口),只需要手持预定义好的 @ConditionalOnXxxx 注解印章的门票,如果验证通过,就会走进 Application Context 大厅。
Spring Boot对@Conditional注解给我们做了细分,这些注解都在org.springframework.boot.autoconfigure.condition包下能找到。
注解详解
我们可以发现这些注解的定义都是这些。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
- 都可以应用在 TYPE 上,也就是说,Spring 自动扫描的一切类 (@Configuration, @Component, @Service, @Repository, or @Controller) 都可以通过添加相应的 @ConditionalOnXxxx 来判断是否加载。
- 都可以应用在 METHOD 上,所以有 @Bean 标记的方法也可以应用这些注解。
- 都是用了 @Conditional 注解来标记,OnBeanCondition 等自定义 Condition 还是实现了 Condition 接口的。
@ConditionalOnBean 和 @ConditionalOnMissingBean
有时候我们需要某个 Bean 已经存在应用上下文时才会加载,那么我们会用到 @ConditionalOnBean 注解。与之相反,我们在上下文不存在某个Bean时需要加载,则会用到@ConditionalOnMissingBean这个注解。
简单使用一下,准备了几个类来测试一下,首先定义几个基础类用户创建对象。
CustomerBeanFactory则是自定义Bean工厂,用来创建或者不创建上面四个Bean。由此可见,我们注册的RegistBean和NotRegistBean会直接影响到Person和Student对象的创建。
- 当NotRegistBean没有注册,Student对象初始化成功。
- 当RegistBean注册成功,Person对象初始化成功。
@Configuration
public class CustomerBeanFactory {
@Bean
public RegistBean initRegistBean() {
return new RegistBean("do regist");
}
/*@Bean
public NotRegistBean initNotRegistBean(){
return new NotRegistBean("not regist");
}*/
@ConditionalOnBean(RegistBean.class)
@Bean
public Person iniPerson() {
return new Person("二狗", 32);
}
@ConditionalOnMissingBean(NotRegistBean.class)
@Bean
public Student initStudent() {
return new Student("小学生", "二年级");
}
}
当我们初始化了RegistBean,没有初始化NotRegistBean时,
通过类MyCondition用来展示结果。
@Configuration
public class MyCondition implements CommandLineRunner {
public void conditionOnBean() {
boolean present = Optional.of(SpringContextUtil.getBean(Person.class)).isPresent();
System.out.println(present ? "person 对象成功加载" : "person 对象未加载");
}
public void conditionOnMissingBean() {
boolean present = Optional.of(SpringContextUtil.getBean(Student.class)).isPresent();
System.out.println(present ? "student 对象成功加载" : "student 对象未加载");
}
@Override
public void run(String... args) throws Exception {
conditionOnBean();
conditionOnMissingBean();
}
}
启动后可以发现,@ConditionalOnBean 和 @ConditionalOnMissingBean都已经生效,Person和Student都初始化完毕。
那么如果条件不满足会怎么样。我们将initNotRegistBean方法注释放开。
@Bean
public NotRegistBean initNotRegistBean(){
return new NotRegistBean("not regist");
}
这个时候@ConditionalOnMissingBean(NotRegistBean.class)条件不成立了,initStudent方法执行不成功。我们启动来康康结果。
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'com.condition.bean.Student' that could not be found.
The following candidates were found but could not be injected:
Bean method 'initStudent' in 'CustomerBeanFactory' not loaded because @ConditionalOnMissingBean
(types: com.condition.bean.NotRegistBean; SearchStrategy: all) found
beans of type 'com.condition.bean.NotRegistBean' initNotRegistBean
Action:
Consider revisiting the entries above or defining a bean of type 'com.condition.bean.Student' in your configuration.
条件不满足时,程序会告诉你@ConditionalOnMissingBean依赖的NotRegistBean已经被发现注册成功了。因此在条件不成功的时候,@ConditionalOnXX注解会抛出异常导致应用终止,那么我在使用的时候一定要慎重。
@ConditionalOnClass 和 @ConditionalOnMissingClass
判断某个类是否存在于 classpath 中。
@ConditionalOnCloudPlatform
只有运行在指定的云平台上才加载指定的 bean,CloudPlatform 是 org.springframework.boot.cloud 下一个 enum 类型的类,大家可以打开自行看看,感觉基本上没啥用
public enum CloudPlatform {
/**
* No Cloud platform. Useful when false-positives are detected.
*/
NONE {
@Override
public boolean isDetected(Environment environment) {
return false;
}
},
/**
* Cloud Foundry platform.
*/
CLOUD_FOUNDRY {
@Override
public boolean isDetected(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION") || environment.containsProperty("VCAP_SERVICES");
}
},
/**
* Heroku platform.
*/
HEROKU {
@Override
public boolean isDetected(Environment environment) {
return environment.containsProperty("DYNO");
}
},
/**
* SAP Cloud platform.
*/
SAP {
@Override
public boolean isDetected(Environment environment) {
return environment.containsProperty("HC_LANDSCAPE");
}
},
/**
* Kubernetes platform.
*/
KUBERNETES {
private static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
private static final String KUBERNETES_SERVICE_PORT = "KUBERNETES_SERVICE_PORT";
private static final String SERVICE_HOST_SUFFIX = "_SERVICE_HOST";
private static final String SERVICE_PORT_SUFFIX = "_SERVICE_PORT";
@Override
public boolean isDetected(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
return isAutoDetected((ConfigurableEnvironment) environment);
}
return false;
}
private boolean isAutoDetected(ConfigurableEnvironment environment) {
PropertySource<?> environmentPropertySource = environment.getPropertySources()
.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
if (environmentPropertySource != null) {
if (environmentPropertySource.containsProperty(KUBERNETES_SERVICE_HOST)
&& environmentPropertySource.containsProperty(KUBERNETES_SERVICE_PORT)) {
return true;
}
if (environmentPropertySource instanceof EnumerablePropertySource) {
return isAutoDetected((EnumerablePropertySource<?>) environmentPropertySource);
}
}
return false;
}
private boolean isAutoDetected(EnumerablePropertySource<?> environmentPropertySource) {
for (String propertyName : environmentPropertySource.getPropertyNames()) {
if (propertyName.endsWith(SERVICE_HOST_SUFFIX)) {
String serviceName = propertyName.substring(0,
propertyName.length() - SERVICE_HOST_SUFFIX.length());
if (environmentPropertySource.getProperty(serviceName + SERVICE_PORT_SUFFIX) != null) {
return true;
}
}
}
return false;
}
};
/**
* Determines if the platform is active (i.e. the application is running in it).
* @param environment the environment
* @return if the platform is active.
*/
public boolean isActive(Environment environment) {
return isEnforced(environment) || isDetected(environment);
}
/**
* Determines if the platform is enforced by looking at the
* {@code "spring.main.cloud-platform"} configuration property.
* @param environment the environment
* @return if the platform is enforced
* @since 2.3.0
*/
public boolean isEnforced(Environment environment) {
String platform = environment.getProperty("spring.main.cloud-platform");
return name().equalsIgnoreCase(platform);
}
/**
* Determines if the platform is detected by looking for platform-specific environment
* variables.
* @param environment the environment
* @return if the platform is auto-detected.
* @since 2.3.0
*/
public abstract boolean isDetected(Environment environment);
/**
* Returns if the platform is behind a load balancer and uses
* {@literal X-Forwarded-For} headers.
* @return if {@literal X-Forwarded-For} headers are used
*/
public boolean isUsingForwardHeaders() {
return true;
}
/**
* Returns the active {@link CloudPlatform} or {@code null} if one is not active.
* @param environment the environment
* @return the {@link CloudPlatform} or {@code null}
*/
public static CloudPlatform getActive(Environment environment) {
if (environment != null) {
for (CloudPlatform cloudPlatform : values()) {
if (cloudPlatform.isActive(environment)) {
return cloudPlatform;
}
}
}
return null;
}
}
@ConditionalOnExpression
多个配置属性一起判断,这个注解就比较合适了。我们举个栗子,根据配置文件里面的条件注册一个Bean
@ConditionalOnExpression("${expression.one} and ${expression.two}")
@Bean
public MutiConditionBean initMutiConditionBean(){
return new MutiConditionBean();
}
在MyConditon类中增加判断方法。
public void conditionalOnExpression(){
boolean present = Optional.of(SpringContextUtil.getBean(MutiConditionBean.class)).isPresent();
System.out.println(present ? "MutiConditionBean 对象成功加载" : "MutiConditionBean 对象未加载");
}
配置文件中,我们写俩配置进去。
expression:
one: true
two: true
执行结果显然成功。
@ConditionalOnJava
只有运行指定版本的 Java 才会加载 Bean
@ConditionalOnJava(JavaVersion.EIGHT)
@Configuration
public class Test {
}
@ConditionalOnJndi
只有指定的资源通过 JNDI 加载后才加载 bean
@ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication
在web应用中加载 or 在非web应用中加载。
@ConditionalOnProperty
这个注解我认为是使用比较广泛的一个注解,根据配置信息加载类。
@ConditionalOnProperty(value = "condition.pro",havingValue = "true",matchIfMissing = true)
@Bean
public RegistOnProperty initRegistOnProperty(){
return new RegistOnProperty();
}
根据配置文件中属性配置的值来决定是否加载RegistOnProperty对象,matchIfMissing设置为true表示当配置文件中不存在该配置时,也会默认加载RegistOnProperty对象,当配置文件中配置false时,会报错。
condition:
pro: true
@ConditionalOnResource
如果我们要加载的 bean 依赖指定资源是否存在于 classpath 中,那么我们就可以使用这个注解
@ConditionalOnResource(resources = "/111.xml")
@Bean
public RegistOnResources initRegistOnResources(){
return new RegistOnResources();
}
当classpath中存在111.xml文件时会加载RegistOnResources对象。
@ConditionalOnSingleCandidate
只有指定类已存在于 BeanFactory 中,并且可以确定单个候选项才会匹配成功 BeanFactory 存在多个 bean 实例,但是有一个 primary 候选项被指定(通常在类上使用 @Primary 注解),也会匹配成功。实质上,如果自动连接具有定义类型的 bean 匹配就会成功 目前,条件只是匹配已经被应用上下文处理的 bean 定义,本身来讲,强烈建议仅仅在 auto-configuration 类中使用这个条件,如果候选 bean 被另外一个 auto-configuration 创建,确保使用该条件的要在其后面运行
@ConditionalOnWarDeployment
当应用程序为传统 WAR 部署时匹配的。
测试源码地址
gitee仓库地址
大家觉得有用的话,记得点赞收藏!!!