定义:在spring框架中用于根据特定条件决定是否创建或者注册某个bean或配置的注解,他们可以根据运行时环境,配置属性,或其他条件来动态的控制bean的创建或者注册。
@Conditional注解
定义:基本上所有的条件注解,都是基于该注解进行的扩展。此注解从Spring4.0之后开始使用,一般用来限制配置类是否生效或者某个bean是否需要注入。
@Conditional源码如下:
@Target({ElementType.TYPE, ElementType.METHOD})//标注此注解可以使用在类和方法上
@Retention(RetentionPolicy.RUNTIME)//此注解在运行时保留
@Documented//注解包含在javadoc中生成的文档中
public @interface Conditional {
Class<? extends Condition>[] value();
}
@interface 是定义注解的关键字。
上述注解定义了一个value的属性,value是一个数组类型,用于存储class对象,这里class对象类型,被限定为Condition类或其子类
看一下conditional接口
@FunctionalInterface//函数接口:只包含一个抽象方法的接口
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
//context:条件上下文、提供了访问环境、Bean定义和其他条件信息的方法。
//metadata:注解元数据,提供了访问条件注解上的属性和其他相关注解信息的方法。
在 Spring 中,matches
方法的返回值表示条件是否满足。条件的判断依据可以根据具体的需求和上下文来确定。
通常情况下,matches
方法的实现可以使用条件上下文(ConditionContext
)和注解元数据(AnnotatedTypeMetadata
)提供的信息进行条件判断。以下是一些常见的判断依据:
- 条件上下文信息:
- 环境信息:可以获取当前运行环境的属性、配置、名称等。
- Bean 定义信息:可以获取正在加载的 Bean 定义的属性、注解等。
- 资源加载器:可以访问类路径资源和文件系统资源等。
- 类加载器:可以加载和检查类、接口和注解等。
- Bean 工厂:可以获取已注册的 Bean 实例、检查它们的类型等。
- 其他自定义信息:可以通过添加自定义属性或其他自定义方式提供额外的信息。
- 注解元数据信息:
- 条件注解属性:可以通过
metadata
对象获取条件注解上定义的属性值,以决定是否满足条件。 - 其他相关注解:可以通过
metadata
对象获取与条件注解关联的其他注解信息,以进一步判断条件。
根据具体的业务需求和上下文信息,可以使用这些信息来编写判断逻辑。常见的判断依据包括但不限于:
- 环境变量的值
- 配置属性的值
- Bean 定义的属性或注解
- 类路径资源或文件系统资源的存在
- 特定的类、接口或注解是否存在
- 条件注解的属性值是否满足特定要求
- 其他自定义条件判断逻辑
需要根据具体的应用场景和条件注解的需求来确定具体的判断依据。
使用:
@Conditional注解一般使用在@Configuration配置类上,用于限制该配置类是否。
该注解也可以定义在方法上和@Bean注解配合使用,用于判断该bean是否需要注入。
示例:
/**
* @author junkai.pan
*/
//注意这里是condition不是conditional 因为@Conditional需要的属性为Condition类或其子类
public class WindowsCondition implements Condition {
@Override
public boolean matches(@NonNull ConditionContext context, @NonNull AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();//获取环境信息
String systemName = environment.getProperty("os.name");//获取os(operating system 操作系统的简写)的name
if (systemName != null) {
return systemName.contains("Windows");//contains返回的结果是boolean 所以可以直接返回
}//请注意这里的windows的w是大写的W 我因为这里弄了半天 emmm
return false;
}
}
上述方法:
1.创建了一个windowsCondition类;
2.实现了condition接口;
3.然后通过context上下文获取运行环境信息;
4.然后获取运行环境中的系统名;
5.通过判断系统名中是不是包含windows来决定条件是否成立。
运行结果:
2023-08-18 14:20:19.064 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : systemName为:Windows 10
2023-08-18 14:20:19.064 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : 条件判断成功。
2023-08-18 14:20:19.067 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : systemName为:Windows 10
2023-08-18 14:20:19.067 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : 条件判断成功。
2023-08-18 14:20:19.327 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : systemName为:Windows 10
2023-08-18 14:20:19.327 INFO 22060 --- [ main] c.s.demo.demo02.WindowsCondition : 条件判断成功。
2023-08-18 14:20:20.069 INFO 22060 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-08-18 14:20:20.082 INFO 22060 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-08-18 14:20:20.082 INFO 22060 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]
2023-08-18 14:20:20.192 INFO 22060 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-08-18 14:20:20.192 INFO 22060 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2174 ms
2023-08-18 14:20:20.247 INFO 22060 --- [ main] c.springBatch.demo.demo02.ConditionTest : condition----ok
扩展:
Springboot中扩展的条件注解一共有14个,都在org.springframework.boot.autoconfigure.condition包下
- @ConditionalOnBean
- @ConditionalOnClass
- @ConditionalOnCloudPlatform
- @ConditionalOnExpression
- @ConditionalOnJava
- @ConditionalOnJndi
- @ConditionalOnMissingBean
- @ConditionalOnMissingClass
- @ConditionalOnNotWebApplication
- @ConditionalOnProperty
- @ConditionalOnResource
- @ConditionalOnSingleCandidate
- @ConditionalOnWarDeployment
- @ConditionalOnWebApplication
通过查看上述各个注解的源码,我们可以了解到,它们都可以作用在类和方法上。
比如:@Configuration、@Component、@Service、@Controller、@Repository、@Mapper等都可以通过添加响应的@ConditionalOnXXXX,来判断是否可以加载。
下面我们对比较常见的条件注解做解释
@ConditionalOnProperty
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
}
作用:
当配置文件,application.propertites/application.yml文件中存在指定属性,该条件为true;
举例:
作用在类上:
@ConditionalOnProperty(prefix = "spring.datasource",name = "type")
@Configuration
@Slf4j
public class TestConditionalOnProperty {
@Bean
public String contionalOnProperty() {
log.info("注入-------contionalOnProperty------ok");
return "ok";
}
}
在上述代码中,表示会去application文件中,读取前缀为:Spring.datasource下,名字为type的key的值,读取到之后,会跟字符串false进行判断,如果获取的值等于 false 那么就返回false,不加载,如果不等于flase,那么就加载
我们的配置文件application.yml中的配置为:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
因为 com.zaxxer.hikari.HikariDataSour != false 所以条件注解判断为true 进行注入。
运行结果:
2023-08-18 15:14:58.422 INFO 3956 --- [ main] c.springBatch.demo.demo02.ConditionTest : condition----ok
2023-08-18 15:14:58.424 INFO 3956 --- [ main] c.s.d.demo02.TestConditionalOnProperty : 注入-------contionalOnProperty------ok
作用在方法上:
@ConditionalOnProperty(prefix = "spring.datasource",name = "type")
@Configuration
@Slf4j
public class TestConditionalOnProperty {
@Bean
@ConditionalOnProperty(prefix = "spring.datasource",value = "username")
public String contionalOnProperty() {
log.info("注入-------contionalOnProperty------ok");
return "ok";
}
}
配置文件:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
username: false
运行结果:
无
上述因为我们读取的值为false,false==false 所以 bean注入失败。
下面我们修改下配置文件,
新配置文件:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
username: root
再次运行:
2023-08-18 15:18:49.103 INFO 18924 --- [ main] c.s.d.demo02.TestConditionalOnProperty : 注入-------contionalOnProperty------ok
使用注意:
- name和value必须存在一个,且不能同时存在。
- 如果havingValue有值,则跟havingValue的值进行比较,相同为true,不同为flase。
使用havingValue条件判断:
@ConditionalOnProperty(prefix = "spring.datasource",name = "type")
@Configuration
@Slf4j
public class TestConditionalOnProperty {
@Bean
@ConditionalOnProperty(name = "spring.datasource.username",havingValue = "root")
public String contionalOnProperty() {
log.info("注入-------contionalOnProperty------ok");
return "ok";
}
}
}
}
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
username: root
结果同样是成功。
注意这里使用的是name,也可以使用value,但是这两个不管在什么条件下,必须要有一个才可以。
- 如果没有havingValue,那么会采用prefix+name 或 prefix+value 的值,去和false比较,如果值等于false 那么失败, 如果不等于false 成功。
- 如果prefix+name或prefix+value 没有匹配到值得话 通过添加 matchMissing 得值来进行判断 如果 matchMissing=true 那么注入 如果matchMissing=flase 那么失败。
@ConditionalOnProperty(prefix = "spring.datasource",name = "type")
@Configuration
@Slf4j
public class TestConditionalOnProperty {
@Bean
@ConditionalOnProperty(name = "spring.datasource.password",matchIfMissing = true)
public String contionalOnProperty() {
log.info("注入-------contionalOnProperty------ok");
return "ok";
}
}
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
username: root
上述运行结果也是true.
@ConditionalOnBean和@ConditionalOnMissingBean
源码:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
Class<?>[] ignored() default {};
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
SearchStrategy search() default SearchStrategy.ALL;
Class<?>[] parameterizedContainer() default {};
}
作用:
当上下文中,存在指定bean得时候,才注入。
当上下文中,不存在指定bean得时候,才注入。
参数介绍:
Class<?>[] value() default {}
:指定要检查是否存在的 Bean 的类型。可以是单个 Class 对象,也可以是 Class 对象数组。只有当至少存在一个指定类型的 Bean 时,条件才会匹配。String[] type() default {}
:类得全限定名,用于指定要检查是否存在的 Bean 类型。Class<? extends Annotation>[] annotation() default {}
:指定要检查是否存在的 Bean 上是否标注了指定的注解。可以是单个注解类型,也可以是注解类型数组。只有当至少存在一个带有指定注解的 Bean 时,条件才会匹配。String[] name() default {}
:指定要检查是否存在的 Bean 的名称。可以是单个字符串值,也可以是字符串数组。只有当至少存在一个指定名称的 Bean 时,条件才会匹配。SearchStrategy search() default SearchStrategy.ALL
:指定搜索 Bean 的策略。可选值为SearchStrategy.ALL
、SearchStrategy.ANCESTORS
和SearchStrategy.CURRENT
。默认值是SearchStrategy.ALL
,表示在当前上下文和所有祖先上下文中搜索 Bean。Class<?>[] parameterizedContainer() default {}
:用于检查是否存在以指定类作为泛型容器的 Bean。可以是单个 Class 对象,也可以是 Class 对象数组。只有当至少存在一个以指定类作为泛型容器的 Bean 时,条件才会匹配。
代码示例:
//这里仅仅是一个普通类,没有控制反转
@Data
public class TestClass {
private String test;
}
@Configuration
@Slf4j
@ConditionalOnBean(TestClass.class)
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
我们得spring容器中没有testClass Bean 所以上述bean注入失败、
相反当我们采用OnMissing就可以了
@Configuration
@Slf4j
@ConditionalOnMissingBean(TestClass.class)
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
2023-08-18 16:31:13.460 INFO 21680 --- [ main] c.s.demo.demo02.TestConditionalOnBean : --------testConditionalOnBean注入成功--------- }
}
下面我们再创建一个bean
@Configuration
@Slf4j
public class TestBean {
@Bean
public String testBeanInstance() {
log.info("-----------testBean注入成功--------");
return "testBean注入成功";
}
}
这时候我们再使用On就成功了
@Configuration
@Slf4j
@ConditionalOnBean(TestBean.class)
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
2023-08-18 16:36:19.513 INFO 11472 --- [ main] c.s.demo.demo02.TestConditionalOnBean : --------testConditionalOnBean注入成功---------
下面我们演示其他参数得使用
name演示:
@Configuration
@Slf4j
@ConditionalOnBean(name = "testBean")
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
value演示:
@Configuration
@Slf4j
@ConditionalOnBean(value = TestBean.class)
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
type演示: 这里注意type是包得全限定名
@Configuration
@Slf4j
@ConditionalOnBean(type = "com.springBatch.demo.demo02.TestBean")
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
annotation演示:
1.自定义注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
2.在某一个bean中使用注解
@Configuration
@Slf4j
@TestAnnotation
public class TestBean {
@Bean
public String testBeanInstance() {
log.info("-----------testBean注入成功--------");
return "testBean注入成功";
}
}
3.测试
@Configuration
@Slf4j
@ConditionalOnBean(annotation = TestAnnotation.class)
public class TestConditionalOnBean {
@Bean
public String testConditionalOnBeanInstance() {
log.info("--------testConditionalOnBean注入成功---------");
return "testConditionalOnBean注入成功";
}
}
4.结果
2023-08-18 17:03:02.954 INFO 18460 --- [ main] c.s.demo.demo02.TestConditionalOnBean : --------testConditionalOnBean注入成功---------
2023
标签:String,default,Conditional,class,Bean,条件,注解,public
From: https://blog.51cto.com/u_16205743/7139740