本文基于SpringBoot 2.5.7版本进行讲解
@Conditional注解是Spring 4.0版本以后引入的新特性,可根据是否满足指定的条件来决定是否进行Bean的实例化及装配。
同时,SpringBoot也提供了基于@Conditional注解的衍生注解,例如:@ConditionalOnBean、@ConditionalOnClass注解等。
@ConditionalOnBean
看了上面的说明,读者可能还是不明白@Conditional注解怎么用?不要紧,以其衍生注解,@ConditionalOnBean注解为例,举个例子来说明下。
先说明下@ConditionalOnBean注解的作用:只有在目标类存在Spring容器中的时候,标注了此注解的类才会被实例化和装配。
我们现有一个Hello类,有一个print()方法会打印hello world。我们希望Spring容器中存在一个Newer类的时候,才将Hello注入到容器中。
Newer类:注意这里只是定义了一个类,但是没有注入Spring容器
public class Newer {
}
Hello类
@Component
@ConditionalOnBean(Newer.class)
public class Hello {
public void print() {
System.out.println("hello word");
}
}
这里标注了@ConditionOnBean注解,表示希望只有Newer类存在Spring容器的时候,才将Hello类注入到Spring容器。
SpringBoot启动类:SpringTestApplication
@SpringBootApplication
public class SpringTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);
Hello hello = context.getBean(Hello.class);
hello.print();
}
}
控制台输出:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xgc.entity.Hello' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
at com.xgc.SpringTestApplication.main(SpringTestApplication.java:15)
可以看到Hello类没有被注入到Spring容器,说明@ConditionOnBean注解生效了。
将Newer类注入到Spring容器
@Component
public class Newer {
}
我们给Newer类加了一个@Component注解,将它注入到Spring容器中。
重新运行SpringBoot启动类
控制台输出结果
hello word
研究如何自定义一个@ConditionalOnXxx注解
上面说了@ConditionOnBean注解是@Conditional的衍生注解。那么我们可不可以自定义一个衍生注解,用来实现和@ConditionalOnBean(Newer.class)一样的效果呢?
先研究下@ConditionalOnBean是如何定义的?
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
看到@Conditional(OnBeanCondition.class)这行代码,我们可以猜到@ConditionalOnBean注解是通过OnBeanCondition来完成Spring容器是否包含指定Bean的判断。
@Conditional注解定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
从@Conditional注解的定义可以看到,注解上传的值是一定Condition接口的实现类。所以,OnBeanCondition类一定实现了Condition接口。
Condition接口
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
可以看到Condition接口是一个函数式接口,只有一个matches()方法,从这个方法的返回值是一个布尔值,可以猜到Spring就会通过这个matches()方法的返回值来决定是否将类注入到Spring容器中。
因此,我们要自定义一个@ConditionalOnXxx注解只要创建一个实现了Condition接口的实现类,并将这个实现类作为参数传给@Conditional接口就可以了。
自定义一个@ConditionalOnBean的衍生注解:@ConditionOnNewer
创建Condition实现类:OnNewerCondition
public class OnNewerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
return beanFactory.containsBean("newer");
}
}
创建自定义注解:ConditionalOnNewer
@Conditional(OnNewerCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ConditionalOnNewer {
}
Newer
@Component
public class Newer {
}
Hello
public class Hello {
public void print() {
System.out.println("hello world");
}
}
配置类:HelloConfiguration
@Configuration
public class HelloConfiguration {
@ConditionalOnNewer
@Bean
public Hello createHello() {
return new Hello();
}
}
SpringBoot启动类:SpringTestApplication
@SpringBootApplication
public class SpringTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);
Hello bean = context.getBean(Hello.class);
bean.print();
}
}
控制台输出如下:
hello world