@Conditional注解概述
@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。
@Conditional注解是由 SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下。
package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { Class<? extends Condition>[] value(); }
从@Conditional注解的源码来看,@Conditional注解可以添加到类上,也可以添加到方法上。在@Conditional注解中,存在一个Condition类型或者其子类型的Class对象数组,Condition是个啥?我们点进去看一下。
package org.springframework.context.annotation; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.core.type.AnnotatedTypeMetadata; @FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }
可以看到,Condition是一个函数式接口,对于函数式接口不了解的同学可以参见【Java8新特性】中的《【Java8新特性】还没搞懂函数式接口?赶快过来看看吧!》一文。也可以直接查看《Java8新特性专栏》来系统学习Java8的新特性。
所以,我们使用@Conditional注解时,需要一个类实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们可以使用我们在@Conditional注解中定义的类来检查。
@Conditional注解的使用场景如下所示。
- 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
- 可以作为元注解,用于自动编写构造性注解;
- 作为方法级别的注解,作用在任何@Bean方法上。
向Spring容器注册bean
不带条件注册bean
我们在PersonConfig2类中新增person01()方法和person02()方法,并为两个方法添加@Bean注解,如下所示。
@Bean("binghe001") public Person person01(){ return new Person("binghe001", 18); } @Bean("binghe002") public Person person02(){ return new Person("binghe002", 20); }
那么,这两个bean默认是否会被注册到Spring容器中呢,我们新建一个测试用例来测试一下。在SpringBeanTest类中新建testAnnotationConfig6()方法,如下所示。
@Test public void testAnnotationConfig6(){ ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class); String[] names = context.getBeanNamesForType(Person.class); Arrays.stream(names).forEach(System.out::println); }
我们运行testAnnotationConfig6()方法,输出的结果信息如下所示。
person binghe001 binghe002
从输出结果可以看出,同时输出了binghe001和binghe002。说明默认情况下,Spring容器会将单实例并且非懒加载的bean注册到IOC容器中。
接下来,我们再输出bean的名称和bean实例对象信息,此时我们在testAnnotationConfig6()方法中添加相应的代码片段,如下所示。
@Test public void testAnnotationConfig6(){ ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class); String[] names = context.getBeanNamesForType(Person.class); Arrays.stream(names).forEach(System.out::println); Map<String, Person> beans = context.getBeansOfType(Person.class); System.out.println(beans); }
再次运行SpringBeanTest类中的testAnnotationConfig6()方法,输出结果如下所示。
person binghe001 binghe002 给容器中添加Person.... {person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}
可以看到,输出了注册到容器的bean。
带条件注册bean
现在,我们就要提出新的需求了,比如,如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002。此时,我们就需要使用@Conditional注解了。
这里,有小伙伴可能会问:如何获取操作系统的类型呢,别急,这个问题很简单,我们继续向下看。
使用Spring的ApplicationContext接口就能够获取到当前操作系统的类型,如下所示。
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); System.out.println(osName);
我们将上述代码整合到SpringBeanTest类中的testAnnotationConfig6()方法中,如下所示。
@Test public void testAnnotationConfig6(){ ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class); Environment environment = context.getEnvironment(); String osName = environment.getProperty("os.name"); System.out.println(osName); String[] names = context.getBeanNamesForType(Person.class); Arrays.stream(names).forEach(System.out::println); Map<String, Person> beans = context.getBeansOfType(Person.class); System.out.println(beans); }
接下来,我们运行SpringBeanTest类中的testAnnotationConfig6()方法,输出的结果信息如下所示。
Windows 10 person binghe001 binghe002 给容器中添加Person.... {person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}
由于我使用的操作系统是Windows 10操作系统,所以在结果信息中输出了Windows 10。
到这里,我们成功获取到了操作系统的类型,接下来,就可以实现:如果当前操作系统是Windows操作系统,则向Spring容器中注册binghe001;如果当前操作系统是Linux操作系统,则向Spring容器中注册binghe002的需求了。此时,我们就需要借助Spring的@Conditional注解来实现了。
要想使用@Conditional注解,我们需要实现Condition接口来为@Conditional注解设置条件,所以,这里,我们创建了两个实现Condition接口的类,分别为WindowsCondition和LinuxCondition,如下所示。
- WindowsCondition
package io.mykit.spring.plugins.register.condition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author binghe * @version 1.0.0 * @description Windows条件,判断操作系统是否是Windows */ public class WindowsCondition implements Condition { /** * ConditionContext:判断条件使用的上下文环境 * AnnotatedTypeMetadata:注释信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //判断是否是Linux系统 //1.获取到IOC容器使用的BeanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2.获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3.获取当前的环境信息 Environment environment = context.getEnvironment(); //4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看 //Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向 //Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义 BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name"); return property.contains("Windows"); } }
- LinuxCondition
package io.mykit.spring.plugins.register.condition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @author binghe * @version 1.0.0 * @description Linux条件,判断操作系统是否是Linux */ public class LinuxCondition implements Condition { /** * ConditionContext:判断条件使用的上下文环境 * AnnotatedTypeMetadata:注释信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //判断是否是Linux系统 //1.获取到IOC容器使用的BeanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2.获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3.获取当前的环境信息 Environment environment = context.getEnvironment(); //4.获取bean定义的注册类,我们可以通过BeanDefinitionRegistry对象查看 //Spring容器中注册了哪些bean,也可以通过BeanDefinitionRegistry对象向 //Spring容器中注册bean,移除bean,查看bean的定义,查看是否包含某个bean的定义 BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name"); return property.contains("linux"); } }
接下来,我们就需要在PersonConfig2类中使用@Conditional注解添加条件了。添加注解后的方法如下所示。
@Conditional({WindowsCondition.class}) @Bean("binghe001") public Person person01(){ return new Person("binghe001", 18); } @Conditional({LinuxCondition.class}) @Bean("binghe002") public Person person02(){ return new Person("binghe002", 20); }
此时,我们再次运行SpringBeanTest类中的testAnnotationConfig6()方法,输出的结果信息如下所示。
Windows 10 person binghe001 给容器中添加Person.... {person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18)}
可以看到,输出结果中不再含有名称为binghe002的bean了,说明程序中检测到当前操作系统为Windows10,没有向Spring容器中注册名称为binghe002的bean。
@Conditional注解也可以标注在类上,标注在类上含义为:满足当前条件,这个类中配置的所有bean注册才能生效,大家可以自行验证@Conditional注解标注在类上的情况
@Conditional的扩展注解
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
@ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
@ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
@ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
@ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
@ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
@ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
@ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
@ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
@ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
@ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnExpression:基于SpEL表达式的条件判断。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
@Conditional 与@Profile 的对比
Spring3.0 也有一些和@Conditional 相似的注解,它们是Spring SPEL 表达式和Spring Profiles 注解 Spring4.0的@Conditional 注解要比@Profile 注解更加高级。@Profile 注解用来加载应用程序的环境。@Profile注解仅限于根据预定义属性编写条件检查。@Conditional注释则没有此限制。
Spring中的@Profile 和 @Conditional 注解用来检查"If…then…else"的语义。然而,Spring4 @Conditional是@Profile 注解的更通用法。
- Spring 3中的 @Profile仅用于编写基于Environment变量的条件检查。配置文件可用于基于环境加载应用程序配置。
- Spring 4 @Conditional注解允许开发人员为条件检查定义用户定义的策略。@Conditional可用于条件bean注册。