Spring Boot2 系列教程(七)理解自动化配置的原理
Spring Boot 中的自动化配置确实够吸引人,甚至有人说 Spring Boot 让 Java 又一次焕发了生机,这话虽然听着有点夸张,但是不可否认的是,曾经臃肿繁琐的 Spring 配置确实让人感到头大,而 Spring Boot 带来的全新自动化配置,又确实缓解了这个问题。
你要是问这个自动化配置是怎么实现的,很多人会说不就是 starter 嘛!那么 starter 的原理又是什么呢?松哥以前写过一篇文章,介绍了自定义 starter:
这里边有一个非常关键的点,那就是条件注解,甚至可以说条件注解是整个 Spring Boot 的基石。
条件注解并非一个新事物,这是一个存在于 Spring 中的东西,我们在 Spring 中常用的 profile 实际上就是条件注解的一个特殊化。
想要把 Spring Boot 的原理搞清,条件注解必须要会用,因此今天松哥就来和大家聊一聊条件注解。
定义
Spring4 中提供了更加通用的条件注解,让我们可以在满足不同条件时创建不同的 Bean,这种配置方式在 Spring Boot 中得到了广泛的使用,大量的自动化配置都是通过条件注解来实现的,查看松哥之前的 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 {
	@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 干货!

Spring Boot2 系列教程(七)理解自动化配置的原理的更多相关文章
- Spring Boot2 系列教程(四)理解Spring Boot 配置文件 application.properties
		
在 Spring Boot 中,配置文件有两种不同的格式,一个是 properties ,另一个是 yaml . 虽然 properties 文件比较常见,但是相对于 properties 而言,ya ...
 - Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent
		
前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId ...
 - Spring Boot2 系列教程 (七) | 使用 Spring Data JPA 访问 Mysql
		
前言 如题,今天介绍 Spring Data JPA 的使用. 什么是 Spring Data JPA 在介绍 Spring Data JPA 之前,首先介绍 Hibernate . Hibernat ...
 - Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache
		
用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场 ...
 - Spring Boot2 系列教程(二十)Spring Boot 整合JdbcTemplate 多数据源
		
多数据源配置也算是一个常见的开发需求,Spring 和 SpringBoot 中,对此都有相应的解决方案,不过一般来说,如果有多数据源的需求,我还是建议首选分布式数据库中间件 MyCat 去解决相关问 ...
 - Spring Boot2 系列教程(十八)Spring Boot 中自定义 SpringMVC 配置
		
用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会自动给我们配置好,但是,真实的项目环境比 ...
 - Spring Boot2 系列教程(一)纯 Java 搭建 SSM 项目
		
在 Spring Boot 项目中,正常来说是不存在 XML 配置,这是因为 Spring Boot 不推荐使用 XML ,注意,并非不支持,Spring Boot 推荐开发者使用 Java 配置来搭 ...
 - Spring Boot2 系列教程(九)Spring Boot 整合 Thymeleaf
		
虽然现在慢慢在流行前后端分离开发,但是据松哥所了解到的,还是有一些公司在做前后端不分的开发,而在前后端不分的开发中,我们就会需要后端页面模板(实际上,即使前后端分离,也会在一些场景下需要使用页面模板, ...
 - Spring Boot2 系列教程(二十九)Spring Boot 整合 Redis
		
经过 Spring Boot 的整合封装与自动化配置,在 Spring Boot 中整合Redis 已经变得非常容易了,开发者只需要引入 Spring Data Redis 依赖,然后简单配下 red ...
 
随机推荐
- 激活Cisco路由器和交换机的right-to-use
			
一.确认当前正在使用license版本 show license #路由器2911测试可行 show license summary #交换机C4503测试可行 show version # ...
 - 小白学Python(5)——python-pptx简单应用
			
python-pptx允许您创建新的演示文稿以及对现有演示文稿进行更改. 实际上,它只允许您对现有演示文稿进行更改:只是,如果您从一个没有幻灯片的演示文稿开始,一开始感觉就像是从头开始创建一个幻灯片. ...
 - 安装tomcat出现的问题
			
今天在安装tomcat时出现了配置环境不对的问题. 在正确配置Tomcat环境变量后,遇到很多次运行startup.bat后,一个窗口一闪而过的. 解决方法: 1.在tomcat的目录下选中start ...
 - java  web基础  js、JSP、servlet之间的传递
			
@ JS 与 JSP :JSP无法直接获取JS的值,只能通过隐藏表单或者dom节点设置. JSP中设置隐藏表单input,或者设置任意一个隐藏或者不隐藏的节点比如div, 而JS就通过document ...
 - JAVA 泛型中的通配符 T,E,K,V,?
			
前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...
 - 【Jmeter】- 使用 jmeter 进行 dubbo 接口测试
			
大家都知道 dubbo 是一个优秀 rpc 框架,它一般(dubbox 除外)通过对外提供 tcp协议的接口进行外部调用.而我们日常使用的测试工具 jmeter 并不支持 dubbo 协议的请求.所以 ...
 - 以股票RSI指标为例,学习Python发送邮件功能(含RSI指标确定卖点策略)
			
本人之前写过若干“给程序员加财商”的系列文,目的是通过股票案例讲述Python知识点,让大家在学习Python的同时还能掌握相关的股票知识,所谓一举两得. 在之前的系列文里,大家能看到K线,均线,成交 ...
 - python控制窗口口字形运动
			
import win32con import win32gui import time import math notepad = win32gui.FindWindow("Photo_Li ...
 - Python——最美丽的编程语言
			
之前总听别人说Python有多厉害,有多好,我是不以为然的.但是当我开始接触Python的时候,我就知道它被誉为最美编程语言不是没有道理的.简短的代码,清晰明了的逻辑,方便快捷的编程工具让它注定会大放 ...
 - SpringBoot + JPA问题汇总
			
实体类有继承父类,但父类没有单独标明注解 异常表现 Caused by: org.hibernate.AnnotationException: No identifier specified for ...