SpringBoot之@ConditionalOnXX注解

目录

  • 前言
  • 注解详解
    • @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包下能找到。
SpringBoot之@ConditionalOnXX注解_第1张图片

注解详解

我们可以发现这些注解的定义都是这些。

@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这个注解。
简单使用一下,准备了几个类来测试一下,首先定义几个基础类用户创建对象。
SpringBoot之@ConditionalOnXX注解_第2张图片
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仓库地址
大家觉得有用的话,记得点赞收藏!!!

你可能感兴趣的:(java,spring,boot,java,spring)