Spring Boot 中的自动化配置确实够吸引人,甚至有人说 Spring Boot 让 Java 又一次焕发了生机,这话虽然听着有点夸张,但是不可否认的是,曾经臃肿繁琐的 Spring 配置确实让人感到头大,而 Spring Boot 带来的全新自动化配置,又确实缓解了这个问题。

你要是问这个自动化配置是怎么实现的,很多人会说不就是 starter 嘛!那么 starter 的原理又是什么呢?松哥以前写过一篇文章,介绍了自定义 starter:

  • 徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!

这里边有一个非常关键的点,那就是条件注解,甚至可以说条件注解是整个 Spring Boot 的基石。

条件注解并非一个新事物,这是一个存在于 Spring 中的东西,我们在 Spring 中常用的 profile 实际上就是条件注解的一个特殊化。

想要把 Spring Boot 的原理搞清,条件注解必须要会用,因此今天松哥就来和大家聊一聊条件注解。

定义

Spring4 中提供了更加通用的条件注解,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的,查看松哥之前的 Spring Boot 文章,凡是涉及到源码解读的文章,基本上都离不开条件注解:

  • 40 篇原创干货,带你进入 Spring Boot 殿堂!

有的小伙伴可能没用过条件注解,但是开发环境、生产环境切换的 Profile 多多少少都有用过吧?实际上这就是条件注解的一个特例。

实践

抛开 Spring Boot,我们来单纯的看看在 Spring 中条件注解的用法。

首先我们来创建一个普通的 Maven 项目,然后引入 spring-context,如下:

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.5.RELEASE</version></dependency>
</dependencies>

然后定义一个 Food 接口:

public interface Food {String showName();
}

Food 接口有一个 showName 方法和两个实现类:

public class Rice implements Food {public String showName() {return "米饭";}
}
public class Noodles implements Food {public String showName() {return "面条";}
}

分别是 Rice 和 Noodles 两个类,两个类实现了 showName 方法,然后分别返回不同值。

接下来再分别创建 Rice 和 Noodles 的条件类,如下:

public class NoodlesCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return context.getEnvironment().getProperty("people").equals("北方人");}
}
public class RiceCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return context.getEnvironment().getProperty("people").equals("南方人");}
}

在 matches 方法中做条件属性判断,当系统属性中的 people 属性值为 ‘北方人’ 的时候,NoodlesCondition 的条件得到满足,当系统中 people 属性值为 ‘南方人’ 的时候,RiceCondition 的条件得到满足,换句话说,哪个条件得到满足,一会就会创建哪个 Bean 。

接下来我们来配置 Rice 和 Noodles :

@Configuration
public class JavaConfig {@Bean("food")@Conditional(RiceCondition.class)Food rice() {return new Rice();}@Bean("food")@Conditional(NoodlesCondition.class)Food noodles() {return new Noodles();}
}

这个配置类,大家重点注意两个地方:

  • 两个 Bean 的名字都为 food,这不是巧合,而是有意取的。两个 Bean 的返回值都为其父类对象 Food。
  • 每个 Bean 上都多了 @Conditional 注解,当 @Conditional 注解中配置的条件类的 matches 方法返回值为 true 时,对应的 Bean 就会生效。

配置完成后,我们就可以在 main 方法中进行测试了:

public class Main {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();ctx.getEnvironment().getSystemProperties().put("people", "南方人");ctx.register(JavaConfig.class);ctx.refresh();Food food = (Food) ctx.getBean("food");System.out.println(food.showName());}
}

首先我们创建一个 AnnotationConfigApplicationContext 实例用来加载 Java 配置类,然后我们添加一个 property 到 environment 中,添加完成后,再去注册我们的配置类,然后刷新容器。容器刷新完成后,我们就可以从容器中去获取 food 的实例了,这个实例会根据 people 属性的不同,而创建出来不同的 Food 实例。

这个就是 Spring 中的条件注解。

进化

条件注解还有一个进化版,那就是 Profile。我们一般利用 Profile 来实现在开发环境和生产环境之间进行快速切换。其实 Profile 就是利用条件注解来实现的。

还是刚才的例子,我们用 Profile 来稍微改造一下:

首先 Food、Rice 以及 Noodles 的定义不用变,条件注解这次我们不需要了,我们直接在 Bean 定义时添加 @Profile 注解,如下:

@Configuration
public class JavaConfig {@Bean("food")@Profile("南方人")Food rice() {return new Rice();}@Bean("food")@Profile("北方人")Food noodles() {return new Noodles();}
}

这次不需要条件注解了,取而代之的是 @Profile 。然后在 Main 方法中,按照如下方式加载 Bean:

public class Main {public static void main(String[] args) {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();ctx.getEnvironment().setActiveProfiles("南方人");ctx.register(JavaConfig.class);ctx.refresh();Food food = (Food) ctx.getBean("food");System.out.println(food.showName());}
}

效果和上面的案例一样。

这样看起来 @Profile 注解貌似比 @Conditional 注解还要方便,那么 @Profile 注解到底是什么实现的呢?

我们来看一下 @Profile 的定义:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {String[] value();
}

可以看到,它也是通过条件注解来实现的。条件类是 ProfileCondition ,我们来看看:

class ProfileCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());if (attrs != null) {for (Object value : attrs.get("value")) {if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {return true;}}return false;}return true;}
}

看到这里就明白了,其实还是我们在条件注解中写的那一套东西,只不过 @Profile 注解自动帮我们实现了而已。

@Profile 虽然方便,但是不够灵活,因为具体的判断逻辑不是我们自己实现的。而 @Conditional 则比较灵活。

结语

两个例子向大家展示了条件注解在 Spring 中的使用,它的一个核心思想就是当满足某种条件的时候,某个 Bean 才会生效,而正是这一特性,支撑起了 Spring Boot 的自动化配置。

好了,本文就说到这里,有问题欢迎留言讨论。

关注公众号【江南一点雨】,专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

Spring Boot2 系列教程(七)理解自动化配置的原理相关推荐

  1. spring boot 项目源码_Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent

    前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent><groupId& ...

  2. Spring Boot2 系列教程(二十二)整合 MyBatis 多数据源

    关于多数据源的配置,前面和大伙介绍过 JdbcTemplate 多数据源配置,那个比较简单,本文来和大伙说说 MyBatis 多数据源的配置. 其实关于多数据源,我的态度还是和之前一样,复杂的就直接上 ...

  3. Spring Boot2 系列教程(十四)CORS 解决跨域问题

    今天和小伙伴们来聊一聊通过CORS解决跨域问题. 同源策略 很多人对跨域有一种误解,以为这是前端的事,和后端没关系,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略. 同源策略是由 Netsca ...

  4. Spring Boot系列教程七:Spring boot集成MyBatis

    一.创建项目 项目名称为 "springboot_mybatis_demo",创建过程中勾选 "Web","MyBatis","M ...

  5. freemarker ftl模板_Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker

    今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...

  6. docker 打包镜像_Spring Boot2 系列教程(四十一)部署 Spring Boot 到远程 Docker 容器

    不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案. 不同于传 ...

  7. druid 手动指定数据源_Spring Boot2 系列教程(二十)整合JdbcTemplate 多数据源

    多数据源配置也算是一个常见的开发需求,Spring 和 SpringBoot 中,对此都有相应的解决方案,不过一般来说,如果有多数据源的需求,我还是建议首选分布式数据库中间件 MyCat 去解决相关问 ...

  8. java 获取项目下的webapp_Spring Boot2 系列教程(一)纯 Java 搭建 SSM 项目

    在 Spring Boot 项目中,正常来说是不存在 XML 配置,这是因为 Spring Boot 不推荐使用 XML ,注意,并非不支持,Spring Boot 推荐开发者使用 Java 配置来搭 ...

  9. Spring Security系列教程03--创建SpringSecurity项目

    前言 在上一章节中,一一哥 已经带大家认识了Spring Security,对其基本概念已有所了解,但是作为一个合格的程序员,最关键的肯定还是得动起手来,所以从本篇文章开始,我就带大家搭建第一个Spr ...

最新文章

  1. 传指针与指针引用的区别
  2. MySQL工具1:mysqladmin
  3. 反射型XSS漏洞的条件+类型+危害+解决
  4. App设计灵感之十二组精美的地图导航App设计案例
  5. 嵌入式linux段错误,在嵌入式Linux上使用C Std Lib时出现异常的段错误
  6. [Python] L1-035. 情人节 团体程序设计天梯赛GPLT
  7. linux下qemu共享文件夹,QEMU Windows来宾和Linux主机之间的共享文件夹
  8. 什么是一致性Hash算法? 1
  9. 测试过程中常用的linux命令之【删除指定的文件行】
  10. c语言输入身高计算标准体重_体质测试 | 身高 / 体重测试评分标准及方法
  11. 【问题导向】GWR与MGWR——以南京市中心城区住宅小区为例
  12. axios安装与基本方法
  13. Java集成云打印机(芯烨云)——文档篇
  14. Android | Tangram动态页面之路(七)硬核的Virtualview
  15. kubectl认证 授权 准入控制
  16. 民办教育未来10年的发展趋势
  17. 计算机科学与技术500分左右的大学,全国所有500分左右的211大学
  18. sapvl10a增强_教你搞定SAP屏幕增强
  19. dropwizard常用属性注解
  20. Xbee使用教程:软件配置及通信python代码

热门文章

  1. 机器学习算法资料汇总
  2. 日立电梯服务器显示地址操作异常,日立电梯服务器地址操作异常
  3. 7-201 输出前n个英文大写字母
  4. 【工具】5 个可以加速开发的 VueUse 库函数
  5. Excel资金日报表中根据借贷方向计算余额以及引用函数的巧妙运用
  6. 又一个35岁中年Java开发莫名被炒,中年危机真的存在吗?
  7. QQ漂流瓶,有点意思
  8. TypeScript 3.7 RC 发布,备受瞩目的 Optional Chaining 来了
  9. nxp电源管理芯片:nxp管理芯片的缺货与涨价
  10. Python之字符串常用操作