SpringBoot自动配置之@Conditional注解 - SpringBoot自动配置(四)

1,277 阅读3分钟

本文基于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