Spring Boot 2从入门到入坟 | 底层注解篇:@Conditional条件装配
发布日期:2021-06-30 17:57:21 浏览次数:3 分类:技术文章

本文共 15768 字,大约阅读时间需要 52 分钟。

在这一篇文章中,我们再来说一个Spring Boot的底层注解,即@Conditional。

@Conditional注解

概述

@Conditional,按照英语翻译过来,应该就是按照什么条件的意思,也就是我们说的条件装配,指的就是当满足@Conditional指定的条件时,才给容器中注入相关的组件,或者是干相应的事。

我们不妨查看一下@Conditional注解的继承关系,如下图所示,发现它还是一个根注解,它下面派生出了非常多的注解。

在这里插入图片描述

接下来,我就来为大家分别介绍一下这些派生注解的功能。当然了,我也不可能一个一个地全面地仔细地介绍到,我就讲几个具有典型代表的注解吧,好不好嘛😥

派生注解

@ConditionalOnBean

该注解指的是当容器中存在指定的某个bean,也就是存在指定的某个组件的时候,我们才干某些事情。

不妨我就在这里以@ConditionalOnBean注解为例,来写几个实际案例,让大家体验一下它的用法。

来到我们的MyConfig配置类中,如下所示。

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件public class MyConfig {
@Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; } @Bean("tom") public Pet tomcatPet() {
return new Pet("tomcat"); }}

可以看到,有一个组件注册的user01方法,我们是使用该方法来向容器中注册一个名字为user01的User类型组件的,那这个组件有没有,这还一定呢。

假设我们给容器中没有注册名字为tom的Pet类型的组件,那tomcatPet方法上面的@Bean注解肯定是要被注释掉的,如下所示,这样tomcatPet方法就是一个常规方法了,也就是说它并不能给容器中注册组件。

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件public class MyConfig {
@Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; }// @Bean("tom") public Pet tomcatPet() {
return new Pet("tomcat"); }}

这时,我们再从容器中获取名字为tom的Pet类型的组件,肯定是获取不到的。所以,现在我们不妨调用容器的containsBean方法来判断一下容器里面是不是包含有某一个组件。那不妨就来看一下容器中是不是还有名字为tom的Pet类型的组件吧!

package com.meimeixia.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.SpringBootConfiguration;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ComponentScan;/** * 主程序类,也叫主配置类 * @author liayun * @create 2021-04-19 4:02 *///@SpringBootApplication@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan("com.meimeixia.boot")public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了当前应用的所有组件 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 这是固定写法哟 // 2. 我们可以来查看下IoC容器里面所有的组件,只要能查找到某一个组件,就说明这个组件是能工作的,至于怎么工作,这就是我们后来要阐述的原理了 String[] names = run.getBeanDefinitionNames(); // 获取所有组件定义的名字 for (String name : names) {
System.out.println(name); } // 3. 从容器中获取组件// Pet tom01 = run.getBean("tom", Pet.class);// Pet tom02 = run.getBean("tom", Pet.class);// System.out.println("组件是否为单实例:" + (tom01 == tom02));//// // 配置类打印:com.meimeixia.boot.config.MyConfig$$EnhancerBySpringCGLIB$$4559f04d@49096b06// MyConfig bean = run.getBean(MyConfig.class);// System.out.println(bean);//// // 如果@Configuration(proxyBeanMethods = true),代理对象调用方法// User user = bean.user01();// User user1 = bean.user01();// System.out.println("(user == user1) = " + (user == user1));//// User user01 = run.getBean("user01", User.class);// Pet tom = run.getBean("tom", Pet.class);// System.out.println("用户的宠物:" + (user01.getPet() == tom));//// // 4. 获取组件// System.out.println("=====================");// String[] beanNamesForType = run.getBeanNamesForType(User.class); // 获取我们给容器中注册的User类型的组件的名字// for (String s : beanNamesForType) {
// System.out.println(s);// }//// DBHelper bean1 = run.getBean(DBHelper.class);// System.out.println(bean1); boolean tom_pet = run.containsBean("tom"); System.out.println("容器中tom组件:" + tom_pet); }}

运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

可以看到,现在容器中确实是没有名字为tom的Pet类型的组件。那容器中有没有名字为user01的User类型的组件呢?不妨再来确定一下。

package com.meimeixia.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.SpringBootConfiguration;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ComponentScan;/** * 主程序类,也叫主配置类 * @author liayun * @create 2021-04-19 4:02 *///@SpringBootApplication@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan("com.meimeixia.boot")public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了当前应用的所有组件 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 这是固定写法哟 // 2. 我们可以来查看下IoC容器里面所有的组件,只要能查找到某一个组件,就说明这个组件是能工作的,至于怎么工作,这就是我们后来要阐述的原理了 String[] names = run.getBeanDefinitionNames(); // 获取所有组件定义的名字 for (String name : names) {
System.out.println(name); } // 3. 从容器中获取组件// Pet tom01 = run.getBean("tom", Pet.class);// Pet tom02 = run.getBean("tom", Pet.class);// System.out.println("组件是否为单实例:" + (tom01 == tom02));//// // 配置类打印:com.meimeixia.boot.config.MyConfig$$EnhancerBySpringCGLIB$$4559f04d@49096b06// MyConfig bean = run.getBean(MyConfig.class);// System.out.println(bean);//// // 如果@Configuration(proxyBeanMethods = true),代理对象调用方法// User user = bean.user01();// User user1 = bean.user01();// System.out.println("(user == user1) = " + (user == user1));//// User user01 = run.getBean("user01", User.class);// Pet tom = run.getBean("tom", Pet.class);// System.out.println("用户的宠物:" + (user01.getPet() == tom));//// // 4. 获取组件// System.out.println("=====================");// String[] beanNamesForType = run.getBeanNamesForType(User.class); // 获取我们给容器中注册的User类型的组件的名字// for (String s : beanNamesForType) {
// System.out.println(s);// }//// DBHelper bean1 = run.getBean(DBHelper.class);// System.out.println(bean1); boolean tom_pet = run.containsBean("tom"); System.out.println("容器中tom组件:" + tom_pet); boolean user01 = run.containsBean("user01"); System.out.println("容器中user01组件:" + user01); }}

重新运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

可以看到,现在容器中确实是有名字为user01的User类型的组件。

但是,我最终希望的效果是这样子的,用户不是要依赖宠物嘛,如果容器中没有宠物类型的组件,那么就别给容器中注册用户类型的组件了。所以,这时,我们就可以使用@ConditionalOnBean这样一个条件注解了,该注解的意思在上面我也说过了,那下面就直接用它吧!

我们不放点进@ConditionalOnBean注解的源码里面去看一看,如下图所示。

在这里插入图片描述

看到没有,它里面有一个Class数组类型的value属性和一个String数组类型的name属性,也就是说,我们可以通过它俩来指定组件的类型与名字。下面,我们不妨来使用一下该注解。

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件public class MyConfig {
@ConditionalOnBean(name = "tom") @Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; }// @Bean("tom") public Pet tomcatPet() {
return new Pet("tomcat"); }}

现在,只有当容器中存在名字为tom的组件时,我们才能给容器中注册名字为user01的User类型的组件,要知道以前都是不管三七二十一,全部把组件注册进来了。

这时,我们再来重新运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

现在看来,加上@ConditionalOnBean(name = "tom")条件装配注解以后,容器中名字为user01的组件也没了。

甚至于呢,我们可以把@ConditionalOnBean(name = "tom")这个注解标注在MyConfig配置类上,如下所示。

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件@ConditionalOnBean(name = "tom")public class MyConfig {
@Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; }// @Bean("tom") public Pet tomcatPet() {
return new Pet("tomcat"); }}

标注在配置类上,就是说只有当容器中有名字为tom的组件时,配置里面的那一堆才生效,否则的话,就都不生效。例如,我们给容器中不注册名字为tom的Pet类型的组件,而是注册一个名字为tom666的Pet类型的组件。

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件@ConditionalOnBean(name = "tom")public class MyConfig {
@Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; }// @Bean("tom") @Bean("tom666") public Pet tomcatPet() {
return new Pet("tomcat"); }}

然后,我们再次重新运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

可以看到,容器中依旧没有名字为user01的组件。这是因为我们容器中都没有名字为tom的组件,何来名字为user01tom666的这两组件呢?也就是说容器中压根就不可能存在名字为user01tom666的这两组件。你要是不信的话,不妨自己来判断一下容器中到底有没有名字为tom666的组件。

package com.meimeixia.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.SpringBootConfiguration;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ComponentScan;/** * 主程序类,也叫主配置类 * @author liayun * @create 2021-04-19 4:02 *///@SpringBootApplication@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan("com.meimeixia.boot")public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了当前应用的所有组件 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 这是固定写法哟 // 2. 我们可以来查看下IoC容器里面所有的组件,只要能查找到某一个组件,就说明这个组件是能工作的,至于怎么工作,这就是我们后来要阐述的原理了 String[] names = run.getBeanDefinitionNames(); // 获取所有组件定义的名字 for (String name : names) {
System.out.println(name); } // 3. 从容器中获取组件// Pet tom01 = run.getBean("tom", Pet.class);// Pet tom02 = run.getBean("tom", Pet.class);// System.out.println("组件是否为单实例:" + (tom01 == tom02));//// // 配置类打印:com.meimeixia.boot.config.MyConfig$$EnhancerBySpringCGLIB$$4559f04d@49096b06// MyConfig bean = run.getBean(MyConfig.class);// System.out.println(bean);//// // 如果@Configuration(proxyBeanMethods = true),代理对象调用方法// User user = bean.user01();// User user1 = bean.user01();// System.out.println("(user == user1) = " + (user == user1));//// User user01 = run.getBean("user01", User.class);// Pet tom = run.getBean("tom", Pet.class);// System.out.println("用户的宠物:" + (user01.getPet() == tom));//// // 4. 获取组件// System.out.println("=====================");// String[] beanNamesForType = run.getBeanNamesForType(User.class); // 获取我们给容器中注册的User类型的组件的名字// for (String s : beanNamesForType) {
// System.out.println(s);// }//// DBHelper bean1 = run.getBean(DBHelper.class);// System.out.println(bean1); boolean tom_pet = run.containsBean("tom"); System.out.println("容器中tom组件:" + tom_pet); boolean user01 = run.containsBean("user01"); System.out.println("容器中user01组件:" + user01); boolean tom666 = run.containsBean("tom666"); System.out.println("容器中tom666组件:" + tom666); }}

再次重新运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

你看到没有,容器中确实没有名字为tom666的组件吧!

所以,我们能得出这样一个结论,条件注解如果加在方法上,那么只有当条件成立以后,这个方法返回的组件才会被注册到容器中,否则,就不注册;如果加在类上,那么只有当条件成立以后,这个类下面的所有配置才能生效,否则,就不生效。相信大家未来在Spring Boot底层一定会遇见到非常多的条件装配注解。

当然了,大家也可以自己去尝试一下其他条件装配注解该怎么使用。只不过,这里我是以@ConditionalOnBean为例来为大家进行讲解的。

@ConditionalOnMissingBean

与@ConditionalOnBean相反的还有一个@ConditionalOnMissingBean,该注解指的是当容器中没有指定的某个组件时我们才干某些事情。

要不,我们也来看看该注解该如何使用吧!例如,现在容器中没有名字为tom的组件,那么我们就把名字为user01tom666的这两组件给注册到容器中,来一个反向操作,如何?

package com.meimeixia.boot.config;import ch.qos.logback.core.db.DBHelper;import com.meimeixia.boot.bean.Pet;import com.meimeixia.boot.bean.User;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/** * @author liayun * @create 2021-04-23 19:42 * */@Import({
User.class, DBHelper.class}) // 这时,就会给容器中自动创建出这两个类型的组件了@Configuration(proxyBeanMethods = true) // 告诉Spring Boot这是一个配置类 == 配置文件//@ConditionalOnBean(name = "tom")@ConditionalOnMissingBean(name = "tom")public class MyConfig {
@Bean // @Bean注解是给容器中添加组件的。添加什么组件呢?以方法名作为组件的id,返回类型就是组件类型,返回的值就是组件在容器中的实例 public User user01() {
User zhangsan = new User("zhangsan", 18); // User类型的组件依赖了Pet类型的组件 zhangsan.setPet(tomcatPet()); return zhangsan; }// @Bean("tom") @Bean("tom666") public Pet tomcatPet() {
return new Pet("tomcat"); }}

此时,我们再次重新运行主程序类,发现IDEA控制台打印结果如下。

在这里插入图片描述

看到没有,现在是容器中没有名字为tom的组件,我们才给容器中注册名字为user01tom666的这两组件,这就是条件装配注解的魅力。

@ConditionalOnClass

该注解指的是当容器中有某一个类的时候,我们才干某些活。

@ConditionalOnMissingClass

与@ConditionalOnClass相反的还有一个@ConditionalOnMissingClass,该注解指的是当容器中没有某一个类时,我们才干某些活,比如我们才给容器中注入某些组件。

@ConditionalOnResource

该注解指的是当项目的类路径里面存在某一个资源的时候我们才干什么。

@ConditionalOnJava

该注解指的是只有运行指定版本的Java,我们才干什么。

@ConditionalOnWebApplication

该注解指的是当我们当前应用是一个Web应用的时候,我们才干什么。

@ConditionalOnNotWebApplication

与@ConditionalOnWebApplication相反的还有一个@ConditionalOnNotWebApplication,该注解指的是当我们当前应用不是一个Web应用的时候,我们才干什么。

@ConditionalOnSingleCandidate

该注解指的是当容器中指定的某个组件只有一个实例,或者它有多个实例但是有一个实例是我们说的这个主实例,即使用@Primary注解标注的实例,我们才干什么。

@ConditionalOnProperty

该注解指的是当配置文件里面配置了某个属性的时候,我们才干某些事情。

以上列举出来的这几个派生注解都是我们常见的,我们以后可能也会在Spring Boot的底层经常看到。在Spring Boot底层,经常会有一些判断,比如我们导入了某些场景,当这个场景里面什么东西配了以后它才帮我们配什么,什么东西没配那它就不帮我们配了,或者我们缺少什么了它才帮我们配什么,等等等等,会有非常非常多这样类似的条件装配。

以上就是我们说的条件装配,随着后来不断的深入学习,我们就会理解条件装配在Spring Boot底层的重要性了。

转载地址:https://liayun.blog.csdn.net/article/details/116231664 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring Boot 2从入门到入坟 | 底层注解篇:使用@ImportResource注解导入Spring配置文件
下一篇:Spring Boot 2从入门到入坟 | 底层注解篇:使用@Import注解导入组件

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月14日 02时49分31秒