Spring Boot 中的自动化配置确实够吸引人,甚至有人说 Spring Boot 让 Java 又一次焕发了生机,这话虽然听着有点夸张,可是不能否认的是,曾经臃肿繁琐的 Spring 配置确实让人感到头大,而 Spring Boot 带来的全新自动化配置,又确实缓解了这个问题。java
你要是问这个自动化配置是怎么实现的,不少人会说不就是 starter 嘛!那么 starter 的原理又是什么呢?松哥之前写过一篇文章,介绍了自定义 starter:spring
这里边有一个很是关键的点,那就是条件注解,甚至能够说条件注解是整个 Spring Boot 的基石。后端
条件注解并不是一个新事物,这是一个存在于 Spring 中的东西,咱们在 Spring 中经常使用的 profile 实际上就是条件注解的一个特殊化。mvc
想要把 Spring Boot 的原理搞清,条件注解必需要会用,所以今天松哥就来和你们聊一聊条件注解。前后端分离
Spring4 中提供了更加通用的条件注解,让咱们能够在知足不一样条件时建立不一样的 Bean,这种配置方式在 Spring Boot 中获得了普遍的使用,大量的自动化配置都是经过条件注解来实现的,查看松哥以前的 Spring Boot 文章,凡是涉及到源码解读的文章,基本上都离不开条件注解:ide
有的小伙伴可能没用过条件注解,可是开发环境、生产环境切换的 Profile 多多少少都有用过吧?实际上这就是条件注解的一个特例。微服务
抛开 Spring Boot,咱们来单纯的看看在 Spring 中条件注解的用法。测试
首先咱们来建立一个普通的 Maven 项目,而后引入 spring-context,以下:spa
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
复制代码
而后定义一个 Food 接口:code
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();
}
}
复制代码
这个配置类,你们重点注意两个地方:
配置完成后,咱们就能够在 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 {
@Override
public 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 干货!