spring4.0之五:@Conditional在满足特定条件下,才会实例化对象,@ConditionalOnProperty @ConditionalOnExpression

一、在Spring的早期版本你可以通过以下方法来处理条件问题
1.1、Spring Expression Language(SPEL)
1.2、使用Profile
二、@Conditional介绍
三、@Conditional使用
四、多属性值
4.1、@ConditionalOnProperty的多属性值
4.2.@ConditionalOnExpression 表达式条件注入
4.3、参考@ConditionalOnProperty自定义注解实现多条件判断

 

这篇文章介绍Spring 4的@Conditional注解。

一、在Spring的早期版本你可以通过以下方法来处理条件问题

  • 3.1之前的版本,使用Spring Expression Language(SPEL)。
  • 3.1版本有个新特性叫profile,用来解决条件问题。

1.1、Spring Expression Language(SPEL)

SPEL有一个三元运算符(if-then-else)可以在配置文件中当作条件语句,如下:

<bean id="flag">  
   <constructor-arg value="#{systemProperties['system.propery.flag'] ?: false }" />  
</bean>  
<bean id="testBean">  
    <property name="prop" value="#{ flag ? 'yes' : 'no' }"/>  
</bean>

 testBean的prop动态依赖于flag的值。

1.2、使用Profile

<!-- 如果没有设置profile,default.xml将被加载 -->  
<!-- 必须放置在配置文件的最底下,后面再也没有bean的定义 -->  
<beans profile="default">  
     <import resource="classpath:default.xml" />  
</beans>  
<!-- some other profile -->  
<beans profile="otherProfile">  
    <import resource="classpath:other-profile.xml" />  
</beans> 

二、@Conditional介绍

官方文档定义:“Indicates that a component is only eligible for registration when all specified conditions match”,意思是只有满足一些列条件之后创建一个bean

除了自己自定义Condition之外,Spring还提供了很多Condition给我们用

@ConditionalOnClass : classpath中存在该类时起效 
@ConditionalOnMissingClass : classpath中不存在该类时起效 
@ConditionalOnBean : DI容器中存在该类型Bean时起效 
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效 
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效 
@ConditionalOnExpression : SpEL表达式结果为true时 
@ConditionalOnProperty : 参数设置或者值一致时起效 
@ConditionalOnResource : 指定的文件存在时起效 
@ConditionalOnJndi : 指定的JNDI存在时起效 
@ConditionalOnJava : 指定的Java版本存在时起效 
@ConditionalOnWebApplication : Web应用环境下起效 
@ConditionalOnNotWebApplication : 非Web应用环境下起效

@Conditional定义

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE, ElementType.METHOD)  
public @interface Conditional{  
    Class<? extends Condition>[] value();
}  

@Conditional注解主要用在以下位置:

  • 类级别可以放在注标识有@Component(包含@Configuration)的类上
  • 作为一个meta-annotation,组成自定义注解
  • 方法级别可以放在标识由@Bean的方法上

如果一个@Configuration的类标记了@Conditional,所有标识了@Bean的方法和@Import注解导入的相关类将遵从这些条件。

condition接口定义如下:

public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked.
     * @return {@code true} if the condition matches and the component can be registered
     * or {@code false} to veto registration.
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

示例1:下面看一个例子:

package com.dxz.demo.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Linux");
    }
}

package com.dxz.demo.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class WindowsCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Windows");
    }
}

我们有两个类LinuxCondition 和WindowsCondition 。两个类都实现了Condtin接口,重载的方法返回一个基于操作系统类型的布尔值。

下面我们定义两个bean,一个符合条件另外一个不符合条件:

package com.dxz.demo.condition;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfiguration {

    @Bean(name = "emailerService")
    @Conditional(WindowsCondition.class)
    public EmailService windowsEmailerService() {
        return new WindowsEmailService();
    }

    @Bean(name = "emailerService")
    @Conditional(LinuxCondition.class)
    public EmailService linuxEmailerService() {
        return new LinuxEmailService();
    }
}

 当符合某一个条件的时候,这里的@Bean才会被初始化。 

测试相关其它类:

package com.dxz.demo.condition;

public interface EmailService {

    public void sendEmail();
}

package com.dxz.demo.condition;

public class WindowsEmailService implements EmailService {

    public void sendEmail() {
        System.out.println("send windows email");
    }

}

package com.dxz.demo.condition;

public class LinuxEmailService implements EmailService {

    public void sendEmail() {
        System.out.println("send linux email");

    }

}


package com.dxz.demo.condition;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class ConditionClient {

    @Autowired
    private EmailService emailService;

    @Scheduled(initialDelay = 3000, fixedDelay = 10000)
    public void test() {
        emailService.sendEmail();
    }
}

结果:

 示例2:@ConditionalOnProperty来控制Configuration是否生效

Spring Boot通过@ConditionalOnProperty来控制Configuration是否生效

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {

    String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用  
  
    String prefix() default "";//property名称的前缀,可有可无  
  
    String[] name() default {};//数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用  
  
    String havingValue() default "";//可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置  
  
    boolean matchIfMissing() default false;//缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错  
  
    boolean relaxedNames() default true;//是否可以松散匹配,至今不知道怎么使用的  
} 
}

通过其两个属性name以及havingValue来实现的,其中name用来从application.properties中读取某个属性值。
如果该值为空,则返回false;
如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。
如果返回值为false,则该configuration不生效;为true则生效。

@Configuration
//在application.properties配置"mf.assert",对应的值为true
@ConditionalOnProperty(prefix="mf",name = "assert", havingValue = "true")
public class AssertConfig {
    @Autowired
    private HelloServiceProperties helloServiceProperties;
    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setMsg(helloServiceProperties.getMsg());
        return helloService;
    }
}

三、@Conditional使用

ConditionalOnProperty的参数详细用法

通过属性值来控制configuration是否生效

@Configuration
@ConditionalOnProperty(name="config.enabled",havingValue = "true")
public class ConfigBean {
    //...
}
// 属性设置
config.enabled=true

3.1、单一属性

(不设置前缀 prefix )

name指定属性名
havingValue 指定属性的值
matchIfMissing 没有指定属性的时候,是否启用configuration,默认不启用

  • 设置属性,优先根据属性值去匹配
    在这里插入图片描述

  • 没设置属性,看 matchIfMissing 的值
    matchIfMissing default false

四、多属性值

设置前缀 prefix, 并且必须设置 name 或者value

4.1、@ConditionalOnProperty的多属性值

这以两个值为例:

@Configuration
@ConditionalOnProperty(prefix="self",name={"a","b"},havingValue = "true")
public class ConfigBean {
    //...
}
// 属性设置
self.a=true
self.b=true
    • 都满足
      即:
      self.a=true
      self.b=true
      则生效

    • 任何一个属性不满足
      即:
      self.a=true
      self.b=false
      或者
      即:
      self.a=abc
      self.b=true
      则不生效

    • 一个满足 一个不设置
      即:
      self.a=true
      或者
      self.b=true
      此时看matchIfMissing
      为true则生效,否则不生效

4.2.@ConditionalOnExpression 表达式条件注入

下面介绍的注解将更加的灵活,基于SPEL表达式的条件注解ConditionalOnExpression

相比较前面的Bean,Class是否存在,配置参数是否存在或者有某个值而言,这个依赖SPEL表达式的,就显得更加的高级了;其主要就是执行Spel表达式,根据返回的true/false来判断是否满足条件

至于SPEL是什么东西,SpEL(Spring Expression Language),即Spring表达式语言。下面以一个简单的demo

4.2.1. @ConditionalOnExpression

接口定义

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {

    /**
     * The SpEL expression to evaluate. Expression should return {@code true} if the
     * condition passes or {@code false} if it fails.
     * @return the SpEL expression
     */
    String value() default "true";
}

4.2.2. 实例测试

用一个简单的例子,当配置参数中,根据是否满足某个条件来决定是否需要加载bean

a. 测试用例

定义一个满足条件和一个不满足的bean

public class ExpressFalseBean {
    private String name;

    public ExpressFalseBean(String name) {
        this.name = name;
    }

    public String getName() {
        return "express bean :" + name;
    }
}

public class ExpressTrueBean {
    private String name;

    public ExpressTrueBean(String name) {
        this.name = name;
    }

    public String getName() {
        return "express bean :" + name;
    }
}

重点关注下bean的配置

@Configuration
public class ExpressAutoConfig {
    /**
     * 当存在配置,且配置为true时才创建这个bean
     * @return
     */
    @Bean
    @ConditionalOnExpression("#{'true'.equals(environment['conditional.express'])}")
    public ExpressTrueBean expressTrueBean() {
        return new ExpressTrueBean("express true");
    }

    /**
     * 配置不存在,或配置的值不是true时,才创建bean
     * @return
     */
    @Bean
    @ConditionalOnExpression("#{!'true'.equals(environment.getProperty('conditional.express'))}")
    public ExpressFalseBean expressFalseBean() {
        return new ExpressFalseBean("express != true");
    }
}

对应的配置如下

conditional.express=true
@ConditionalOnExpression("'true")
当括号中的内容为true时,使用该注解的类被实例化,支持语法如下:

@ConditionalOnExpression("${mq.cumsumer.enabled}==1&&${rabbitmq.comsumer.enabled:true}")

@ConditionalOnExpression("'${mq.comsumer}'.equals('rabbitmq')")

4.3、参考@ConditionalOnProperty自定义注解实现多条件判断

需求:当下面2个配置项都为true时,

pool.sync.enable: true
pool.monitor:true

 

方式1:@ConditionalOnProperty(name = { "pool.sync.enable", "pool.monitor" }, havingValue = "true", matchIfMissing = false)

或者

方式2:

才会创建ThreadPoolMonitor.java的bean。下面的代码实现:

import java.lang.annotation.*;

import org.springframework.context.annotation.Conditional;

@Retention(RetentionPolicy.RUNTIME) 
@Target({ ElementType.TYPE, ElementType.METHOD }) 
@Documented 
@Conditional(ThreadMonitorOnPropertyCondition.class) 
public @interface ThreadMonitorConditionalOnProperty {
    String enable(); 
    String enableValue();
    
    String monitor(); 
    String monitorValue();
}

2、

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

class ThreadMonitorOnPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String name = attribute(metadata, "enable");
        String enableValue = attribute(metadata, "enableValue");

        String enableRealValue = getProperty(context, name);
        if (enableValue.equals(enableRealValue)) {
            String monitorName = attribute(metadata, "monitor");
            String monitorValue = attribute(metadata, "monitorValue");
            String monitorRealValue = getProperty(context, monitorName);
            if (monitorValue.equals(monitorRealValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取注解中的值
     * @param metadata
     * @param name
     * @return
     */
    private String attribute(AnnotatedTypeMetadata metadata, String name) {

          if(!metadata.isAnnotated(MySQLDatasourceConditionalOnProperty.class.getName())) {
              return null;
          }

return (String) metadata.getAnnotationAttributes(ThreadMonitorConditionalOnProperty.class.getName()).get(name);
    }

    /**
     * 获取配置变量值
     * @param context
     * @param name
     * @return
     */
    private String getProperty(ConditionContext context, String name) {
        return context.getEnvironment().getProperty(name);
    }
}

3、测试

@Configuration
@ThreadMonitorConditionalOnProperty(enable = "pool.sync.enable",enableValue = "true", monitor = "pool.monitor", monitorValue = "true")
public class ThreadPoolMonitor {

配置文件:

pool.monitor: true
#sync pool
pool.sync.enable: true
pool.sync.corePoolSize: 10
pool.sync.maxPoolSize: 50
pool.sync.keepAliveSeconds: 60
pool.sync.queueCapacity: 0

 

 

 

posted on 2017-09-08 14:43  duanxz  阅读(9627)  评论(0编辑  收藏  举报