04-详解SpringBoot自动装配的原理,依赖属性配置的实现,源码分析
自动装配原理
依赖属性配置
提供Bean用来封装配置文件中对应属性的值
@Data
public class Cat {private String name;private Integer age;
}
@Data
public class Mouse {private String name;private Integer age;
}
cartoon:cat:name: "图多盖洛"age: 5mouse:name: "泰菲"age: 1
读取yml文件中的数据,将业务功能Bean运行需要的数据抽取出来封装到CartoonProperties对象中
- 缺点: 要想封装yml文件中的数据这个Bean必须由Spring管控,但其实如果我们没有导入业务功能Bean就没必要读取yml文件中的数据
Component
@ConfigurationProperties(prefix = "cartoon")
@Data // 需要给Cat和Mouse提供对应的getter和setter方法,才能把yml文件中的数据注入到Cat和Mouse对象中
public class CartoonProperties { private Cat cat;private Mouse mouse;
}
在业务Bean中根据需要读取CartoonProperties对象中的数据,@EnableConfigurationProperties开启属性类的配置绑定功能并强制把其注册到容器中
- 如果开发者在yml文件中配置了对应的属性的值就使用配置的值,如果没有配置就使用默认值
@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{private Cat cat;private Mouse mouse;private CartoonProperties cartoonProperties;// 自动注入,如果有参的构造方法只有一个@Autowired注解可以省略public CartoonCatAndMouse(CartoonProperties cartoonProperties){this.cartoonProperties = cartoonProperties;cat = new Cat();// 如果开发者在yml文件中配置了对应的属性就使用配置的值,如果没有配置就使用默认值cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName()) ? cartoonProperties.getCat().getName() : "tom");cat.setAge(cartoonProperties.getCat()!=null && cartoonProperties.getCat().getAge()!=null ? cartoonProperties.getCat().getAge() : 3);mouse = new Mouse();mouse.setName(cartoonProperties.getMouse()!=null && StringUtils.hasText(cartoonProperties.getMouse().getName()) ? cartoonProperties.getMouse().getName() : "jerry");mouse.setAge(cartoonProperties.getMouse()!=null && cartoonProperties.getMouse().getAge()!=null ? cartoonProperties.getMouse().getAge() : 4);}public void play(){System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");}
}
使用@Import方式导入业务Bean,避免业务Bean强制加载,根据需要导入,降低Spring管控Bean的强度
- 缺点: 自动配置类我们也没必要强制加载成容器的Bean,应当是满足某种条件时才加载
@SpringBootApplication
@Import(CartoonCatAndMouse.class)
public class App {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(App.class);CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);bean.play();System.out.println(ctx.getBean(Cat.class));}
}
自动配置源码分析
@SpringBootApplication底层相关的注解
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}
}
| SpringBootApplication底层注解 | 注解的底层注解 |
|---|---|
| @SpringBootConfiguration | @Configuration,说明Spring Boot程序的启动类也是一个配置类 |
| @EnableAutoConfiguration | @AutoConfigurationPackage --> @ Import(AutoConfigurationPackages.Registrar.class) @Import(AutoConfigurationImportsSelector.class) |
| @ComponentScan | 指定扫描过滤的规则FilterType.CUSTOM和TypeExcludeFilter.class等, 默认扫描主程序所在包及其子包下的所有组件 |
@Import(AutoConfigurationPackages.Registrar.class)注解: 设置启动类的包作为基础扫描包, 后续将该包及其子包下注解标识类注册成Bean添加到容器中
AutoConfigurationPackages.Registrar是AutoConfigurationPackages(抽象类)的静态内部类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {// 记录所有要扫描的包,启动类所在的包及其子包String[] basePackages() default {};Class<?>[] basePackageClasses() default {};static classimplementsImportBeanDefinitionRegistrar,DeterminableImportsRegistrar{@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// metadata是元数据,可以获取启动类上的所有注解信息// new PackageImports(metadata).getPackageNames()获取到的就是启动类所在的包register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));}}
}public static void register(BeanDefinitionRegistry registry, String... packageNames){// 判断容器中是否加载过AutoConfigurationPackagesif (registry.containsBeanDefinition(BEAN)){BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);beanDefinition.addBasePackages(packageNames);}else{// 注册一个叫com...AutoConfigurationPackages的Bean,将要扫描的包封装到BasePackagesBeanDefinition对象中registry.registerBeanDefinition(BEAN,new BasePackagesBeanDefinition(packageNames));}
}static final class BasePackagesBeanDefinition extends GenericBeanDefinition {private final Set<String> basePackages = new LinkedHashSet<>();BasePackagesBeanDefinition(String... basePackages) {setBeanClass(BasePackages.class);setRole(BeanDefinition.ROLE INFRASTRUCTURE);addBasePackages(basePackages);}
}
@Import(AutoConfigurationImportSelector.class): 指定工程启动时需要向容器中添加的所有自动配置类XxxAutoConfiguration
- 在
spring-boot-autoconfigure-xxx.jar包里面的META-INF/spring.factories目录下存放了工程启动时需要加载的所有类(含自动配置类)

// Spring中的Bean只要实现了XxxAware相关的接口并实现接口的setXxx方法,就可以在当前Bean中使用对应的对象
// Ordered表示加载的顺序,因为有些Bean加载的时候是要依赖其他Bean的,每个Bean都有对应的加载顺序
// DeferredImportSelector表示推迟的导入选择器
public class AutoConfigurationImportSelector implements DeferredImportSelector,BeanClassLoaderAware,ResourceLoaderAware,BeanFactoryAware,EnvironmentAware,Ordered { private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}// 使用ApplicationContext接口中的相关方法String[] beans = applicationContext.getBeanDefinitionNames();for (String bean : beans) {System.out.println(bean);}
}@Override
// selectImports方法的返回值是一个String类型数组,数组的元素就是我们要批量导入的组件
public String[] selectImports (AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;} AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata){// 判断元注解也就是我们的启动类是否是可用的if(!isEnabled(annotationMetadata)) {return EMPTY_ENTRY:} // 获取启动类上的所有注解及其属性,@EnableAutoConfiguration注解有exclude,excludeName两个属性可以按照Class对象和全类名排除不需要加载的类 AnnotationAttributes attributes = getAttributes(annotationMetadata);// 获取候选的配置,读取META-INF/spring.factories中的数据,将所有自动配置类的全类名添加到一个List集合中并返回List<String> configurations= getCandidateConfigurations(annotationMetadata, attributes);// 排除不需要导入的配置类configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations,exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations,exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}// 调用loadFactoryNames方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesloader.loadFactoryNames(getSpringFactoriesloaderFactoryClass(),getBeanClassLoader());Assert.notEmpty(configurations, message: "No auto configuration classes found in META-INF/spring.factories, If you " + "are using a custom packaging, make sure that file is correct.");return configurations;
}// 调用loadSpringFactories方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable Classloader classloader){// 获取类加载器ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == nul1) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}// 获取字符串String factoryTypeName = factoryType.getName();return loadSpringFactories(classloaderToUse).getorDefault(factoryTypeName, Collections,emptylist());
}// 最终调用的方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) (
Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();
try {// 通过类加载器加载外部资源,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面的META-INF/spring.factories目录下的自动配置类Enumeration<URL> urls = classLoader,getResources(FACTORIES_ESOURCE_OCATION);while (urls.hasMoreElements()){URL url= urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);}}
测试XxxAutoConfiguration
AopAutoConfiguration自动配置类的生效条件
@Configuration(proxyBeanMethods = false)
// 判断是否存在一个配置文件有spring.aop的前缀属性,默认是存在的
@ConditionalOnProperty(prefix = "spring.aop",name = "auto",havingValue = "true",matchIfMissing = true
)
public class AopAutoConfiguration {public AopAutoConfiguration() {}...
}
RedisAutoConfiguration自动配置类的生效条件及其绑定的属性配置类 RedisProperties
@Configuration(proxyBeanMethods = false)
// 要加载RedisAutoConfiguration必须有RedisOperations,这个类在spring-boot-starter-data-redis中
@ConditionaionClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({LettuceConnectionConfiguration,class, JedisConnectionConfiguration,class})
public class RedisAutoConfiguration {@Bean@ConditionalOnMissingBean(name = "redisTemplate")@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<0bject, 0bject> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);return template}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}
}
RedisProperties封装了yml文件中的spring.redis属性的值
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {private int database = 0;private String url;private String host = "localhost";// .....
}
自动配置流程
第一步: 将开发过程使用的常用技术列表整理成一个技术集A即所有的自动配置类XxAutoConfiguration,工程启动时默认会全部加载到内存中
- 这些自动配置类虽然会全部加载到内存中,但不会全部生效, 只有满足实际条件的自动配置类及其内部的才会注册成容器中的Bean
- 每个自动配置类都对应一个属性配置类,用来封装配置文件中指定前缀的属性值,自动配置类需要用时会从
xxxProperties对象中获取
第二步: 将这些常用技术需要设置的参数整理成一个设置集B即所有的属性配置类xxxxProperties用来封装yml文件中对应属性的值
第三步: 开放设置集B的配置覆盖接口,若开发者在yml文件中配置了某属性的值,对应属性配置对象中的对应属性就可以获取到值,如果没有配置对应属性为默认值
- SpringBoot默认会在底层配好所有的组件, 但是如果用户自己配置了以用户的优先,如直接通过定义@Bean替换底层的组件或者去修改这个组件获取的配置文件值
第四步: 生效的自动配置类从对应属性配置对象中获取值然后为要创建的组件赋值(约定大于配置),若获取的属性值是null就使用默认值,如果不是就使用获取到的值
第五步: 加载用户自定义的Bean和导入的其他坐标,检测每个自动配置类的加载条件是否满足, 最后初始化SpringBoot的基础环境
变更自动配置
@SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration")// 排除加载的自动配置类
//@Import(CartoonCatAndMouse.class) // 根据条件装配这个自动配置类
public class App {public static void main(String[] args) {ConfigurableApplicationContext ctx = SpringApplication.run(App.class);CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);bean.play();System.out.println(ctx.getBean(Cat.class));}
}
SpringBoot中自带的自动配置类有130个,后面的技术若想要实现自动配置功能需要手动在t工程中的resources/META-INF目录下添加spring.factories文件
- SpringBoot默认会扫描我们当前工程里面所有的
META-INF/factories文件(每个jar包都是一个工程)
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.itheima.bean.CartoonCatAndMouse
在配置文件中排除加载的自动配置类
spring:autoconfigure:exclude:- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
相关文章:
04-详解SpringBoot自动装配的原理,依赖属性配置的实现,源码分析
自动装配原理 依赖属性配置 提供Bean用来封装配置文件中对应属性的值 Data public class Cat {private String name;private Integer age; }Data public class Mouse {private String name;private Integer age; }cartoon:cat:name: "图多盖洛"age: 5mouse:name: …...
[100天算法】-不同路径 III(day 73)
题目描述 在二维网格 grid 上,有 4 种类型的方格:1 表示起始方格。且只有一个起始方格。 2 表示结束方格,且只有一个结束方格。 0 表示我们可以走过的空方格。 -1 表示我们无法跨越的障碍。 返回在四个方向(上、下、左、右&#…...
【c++随笔12】继承
【c随笔12】继承 一、继承1、继承的概念2、3种继承方式3、父类和子类对象赋值转换4、继承中的作用域——隐藏5、继承与友元6、继承与静态成员 二、继承和子类默认成员函数1、子类构造函数 二、子类拷贝构造函数3、子类的赋值重载4、子类析构函数 三、单继承、多继承、菱形继承1…...
Excel中使用数据验证、OFFSET实现自动更新式下拉选项
在excel工作簿中,有两个Sheet工作表。 Sheet1: Sheet2(数据源表): 要实现Sheet1中的“班级”内容,从数据源Sheet2中获取并形成下拉选项,且Sheet2中“班级”内容更新后,Sheet1中“班…...
Android修行手册 - 可变参数中星号什么作用(冷知识)
点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&…...
Python与ArcGIS系列(三)视图缩放
目录 0 简述1 在所有图层中缩放至所选要素2 在单独图层中缩放至所选要素3 改变地图范围0 简述 本篇介绍如何利用arcpy实现缩放视图到所选要素以及改变地图范围功能。 对于以及创建的选择集数据,通常需要进行缩放以更好地显示所选要素,要素缩放可分为两种:第一种是在所有图层…...
[ASP]数据库编辑与管理V1.0
本地测试:需要运行 ASP专业调试工具(自己搜索下载) 默认登陆口令:admin 修改口令:打开index.asp找到第3行把admin"admin"改成其他,如admin"abc123" 程序功能齐全,代码精简…...
MyBatis Plus整合Redis实现分布式二级缓存
MyBatis缓存描述 MyBatis提供了两种级别的缓存, 分别时一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,只在SqlSession对象内部存储缓存数据,如果SqlSession对象不一样就无法命中缓存,二级缓存是mapper级别的缓存ÿ…...
如何帮助 3D CAD 设计师实现远程办公
当 3D CAD 设计师需要远程办公时,他们可能需要更强的远程软件,以满足他们的专业需求。比如高清画质,以及支持设备重定向、多显示器支持等功能。3D CAD 设计师如何实现远程办公?接下来我们跟随 Platinum Tank Group 的故事来了解一…...
如何在 Idea 中修改文件的字符集(如:UTF-8)
以 IntelliJ IDEA 2023.2 (Ultimate Edition) 为例,如下: 点击左上角【IntelliJ IDEA】->【Settings…】,如下图: 从弹出页面的左侧导航中找到【Editor】->【File Encodings】,并将 Global Encoding、Project E…...
【C++】单例模式【两种实现方式】
目录 一、了解单例模式前的基础题 1、设计一个类,不能被拷贝 2、设计一个类,只能在堆上创建对象 3、设计一个类,只能在栈上创建对象 4、设计一个类,不能被继承 二、单例模式 1、单例模式的概念 2、单例模式的两种实现方式 …...
php的api接口token简单实现
<?php // 生成 Token function generateToken() {$token bin2hex(random_bytes(16)); // 使用随机字节生成 tokenreturn $token; } // 存储 Token(这里使用一个全局变量来模拟存储) $tokens []; // 验证 Token function validateToken($token) {gl…...
CCNA课程实验-13-PPPoE
目录 实验条件网络拓朴需求 配置实现基础配置模拟运营商ISP配置ISP的DNS配置出口路由器OR基础配置PC1基础配置 出口路由器OR配置PPPOE拨号创建NAT(PAT端口复用) PC1测试结果 实验条件 网络拓朴 需求 OR使用PPPoE的方式向ISP发送拨号的用户名和密码,用户名…...
cocosCreator 之 Bundle使用
版本: v3.4.0 语言: TypeScript 环境: Mac Bundle简介 全名 Asset Bundle(简称AB包),自cocosCreator v2.4开始支持,用于作为资源模块化工具。 允许开发者根据项目需求将贴图、脚本、场景等资源划分在 Bundle 中&am…...
分类网络搭建示例
搭建CNN网络 本章我们来学习一下如何搭建网络,初始化方法,模型的保存,预训练模型的加载方法。本专栏需要搭建的是对分类性能的测试,所以这里我们只以VGG为例。 请注意,这里定义的只是一个简陋的版本,后续一…...
为 Ubuntu 虚拟机构建 SSH 服务器
以校园网环境和VMware为例,关键步骤如下: 安装 SSH 服务: 打开 Ubuntu 虚拟机。打开终端。输入命令 sudo apt-get update 更新软件包列表。输入命令 sudo apt-get install openssh-server 安装 SSH 服务。 配置 SSH 服务: 编辑配…...
SpringBoot--中间件技术-2:整合redis,redis实战小案例,springboot cache,cache简化redis的实现,含代码
SpringBoot整合Redis 实现步骤 导pom文件坐标 <!--redis依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>yaml主配置文件,配置…...
linux rsyslog配置文件详解
1.rsyslog配置文件简介 linux rsyslog配置文件/etc/rsyslog.conf分为三部分:MODULES、GLOBAL DIRECTIVES、RULES ryslog模块说明 模块说明MODULES指定接收日志的协议和端口。若要配置日志服务器,则需要将相应的配置项注释去掉。GLOBAL DIRECTIVES主要用来配置日志模版。指定…...
wordpress是什么?快速搭网站经验分享
作者主页 📚lovewold少个r博客主页 ⚠️本文重点:c入门第一个程序和基本知识讲解 👉【C-C入门系列专栏】:博客文章专栏传送门 😄每日一言:宁静是一片强大而治愈的神奇海洋! 目录 前言 wordp…...
排序 算法(第4版)
本博客参考算法(第4版):算法(第4版) - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 本文用Java实现相关算法。 我们关注的主要对象是重新排列数组元素的算法,其中每个元素…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
一、前言 在HarmonyOS 5的应用开发模型中,featureAbility是旧版FA模型(Feature Ability)的用法,Stage模型已采用全新的应用架构,推荐使用组件化的上下文获取方式,而非依赖featureAbility。 FA大概是API7之…...
