简介
说明
本文介绍SpringBoot如何将某个类根据条件注入容器。比如:如果某个类不存在,则将类注入;如果某个配置没开启,则不注入。
本文的条件注解,如无特殊说明,都可以放到如下几个地方:有@Component的类、有@Configuration的类、有@Bean的方法。
相关网址
源码中的使用
在Spring Boot的源码中,比如涉及到Http编码的自动配置、数据源类型的自动配置等大量的使用到了@ConditionalOnProperty注解。
HttpEncodingAutoConfiguration类中部分源代码:
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(HttpProperties.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(CharacterEncodingFilter.class) @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { // 省略内部代码 }
DataSourceConfiguration类中部分代码:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true) static class Tomcat { // 省略内部代码 }
已有的条件注解
注解大全
注解 | 说明 | 示例 |
@ConditionalOnProperty | 要求配置属性匹配条件 | @ConditionalOnProperty( prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true) |
@ConditionalOnBean | 容器中存在对应实例时,返回true。 当给定的类型、类名、注解、昵称在beanFactory中存在时返回true。 若不指定name,则为本bean的名字。 | @ConditionalOnBean(Abc.class) 或 @ConditionalOnBean(name=”abc”) |
@ConditionalOnMissingBean | 容器中不存在对应实例时,返回true。 当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true。各类型间是or的关系。 若不指定name,则为本bean的名字。 | |
@ConditionalOnClass | 当给定的类在类路径上存在时返回true,各类型间是and的关系 | @ConditionalOnClass(Abc.class) |
@ConditionalOnMissingClass | 当给定的类在类路径上不存在时返回true,各类型间是and的关系 | |
@ConditionalOnSingleCandidate | 当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true | |
@ConditionalOnCloudPlatform | 当所配置的CloudPlatform为激活时返回true | |
@ConditionalOnExpression | spel表达式执行为true | 详见下方。 |
@ConditionalOnJava | 运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配 | |
@ConditionalOnJndi | 给定的jndi的Location 必须存在一个.否则,返回不匹配 | |
@ConditionalOnNotWebApplication | web环境不存在时 | |
@ConditionalOnWebApplication | web环境存在时 | |
@ConditionalOnResource | 要求制定的资源存在 |
详解
@ConditionalOnProperty
@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也会正常加载;反之报错 }
单个值(key必须存在,且value必须是指定值)
@ConditionalOnProperty("config1.enable", havingValue="true")
单个值(key必须存在,不关心value)
@ConditionalOnProperty("config1.enable")
单个值(key不存在时可以匹配;key存在,value为指定值也匹配)
@ConditionalOnProperty(name = {"config1.enable"}, havingValue="true", matchIfMissing=true)
多个值(多个key必须存在,且value必须都是指定值)
@ConditionalOnProperty(name={"config1.enable","config.all"}, havingValue="true")
多个值(多个key必须存在,不关心value)
@ConditionalOnProperty(name={"config1.enable","config.all"})
注意事项
name这个字段,必须与yml的配置一模一样(不支持宽松匹配)。比如:。比如:
application.yml
custom.nick-name: Tony
Java实体类字段
private String nickName;
条件注解
@ConditionalOnProperty(name={"custom.nick-name"}, havingValue="true")
@ConditionalOnClass
用法1:通过class指定
注意:通过class指定时必须放到有@Configuration的配置类上,不能放到@Bean注解的方法上。
- 如果放到方法上,启动时会报错:ClassNotFoundException。
- 如果放到配置类上,不会报错。因为里边捕获了这种异常,不会影响启动。
import a.b.c.Xxx; @ConditionalOnClass(Xxx.class)
也可以指定多个
import a.b.c.Xxx; @ConditionalOnClass({Xxx.class, Yyy.class})
用法2:通过类的全路径名指定
注意:此方法是在SpringBoot2.2版本开始才有的,要注意兼容性。通过类的全路径名指定时可以放到有@Configuration的配置类上,也可以放到@Bean注解的方法上。
@ConditionalOnClass("a.b.c.Xxx")
也可以指定多个
@ConditionalOnClass({"a.b.c.Xxx", "a.b.c.Yyy"})
源码示例
以openfeign的自动配置类为例:
package org.springframework.cloud.openfeign; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import javax.annotation.PreDestroy; import com.fasterxml.jackson.databind.Module; import feign.Client; import feign.Feign; import feign.RequestInterceptor; import feign.httpclient.ApacheHttpClient; import feign.okhttp.OkHttpClient; import okhttp3.ConnectionPool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor; import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.PageJacksonModule; import org.springframework.cloud.openfeign.support.SortJacksonModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.oauth2.client.OAuth2ClientContext; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Feign.class) @EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class }) @Import(DefaultGzipDecoderConfiguration.class) public class FeignAutoConfiguration { // 省略其他代码 @Configuration(proxyBeanMethods = false) @ConditionalOnClass(OkHttpClient.class) @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") protected static class OkHttpFeignConfiguration { private okhttp3.OkHttpClient okHttpClient; @Bean @ConditionalOnMissingBean(ConnectionPool.class) public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { Integer maxTotalConnections = httpClientProperties.getMaxConnections(); Long timeToLive = httpClientProperties.getTimeToLive(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); } @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects) .connectionPool(connectionPool).build(); return this.okHttpClient; } @PreDestroy public void destroy() { if (this.okHttpClient != null) { this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(okhttp3.OkHttpClient client) { return new OkHttpClient(client); } } }
注意点:
如果项目中没有引入这个包的话,在Idea中可以看到,它是红色的。但项目在启动时并不会报错,可以正常启动。
原因是:实际我们在引入这个依赖的时候,它的jar包是已经编译好了的。而我们现在这样查看是源码方式查看,不是查看的其jar包的class。
@ConditionalOnExpression
或/与
config1.enabled为true或者config2.enabled 为true
@ConditionalOnExpression(“${config1.enabled:false} || ${config2.enabled:false}”)
与也是类似的:换成&&即可。
计算
@ConditionalOnExpression(“${mq.cumsumer.enabled}==1 && ${rabbitmq.comsumer.enabled:true}”)
取反
config1.enabled为true而且config2.enabled 为false
@ConditionalOnExpression(“${config1.enabled:false} && !${config2.enabled:false}”)
equals
@ConditionalOnExpression(“‘${mq.comsumer}’.equals(‘rabbitmq’)”)
分级
例2:若局部有配置,按局部配置。否则,按全局配置。若全局、局部都没配置,则不导入。
本处:config2.enabled为局部,config.all.enabled为全局
@ConditionalOnExpression(value = “${config2.enabled:${config.all.enabled:false}}”)
请先
!