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: 5
  mouse:
    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.RegistrarAutoConfigurationPackages(抽象类)的静态内部类
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    // 记录所有要扫描的包,启动类所在的包及其子包
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};

    static classimplementsImportBeanDefinitionRegistrar,DeterminableImportsRegistrar{
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // metadata是元数据,可以获取启动类上的所有注解信息
            // new PackageImports(metadata).getPackageNames()获取到的就是启动类所在的包
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    }
}


public static void register(BeanDefinitionRegistry registry, String... packageNames){
    // 判断容器中是否加载过AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)){
        BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
        beanDefinition.addBasePackages(packageNames);
    }
    else{
        // 注册一个叫com...AutoConfigurationPackages的Bean,将要扫描的包封装到BasePackagesBeanDefinition对象中
        registry.registerBeanDefinition(BEANnew 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,ResourceLoaderAwareBeanFactoryAwareEnvironmentAwareOrdered {   
    private ApplicationContext applicationContext;
    @Override
    public 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<ObjectObject> 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137457.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

若依Linux与Docker集群部署

若依Linux集群部署 1. 若依2.MYSQL Linux环境安装2.1 MYSQL数据库部署和安装2.2 解压MYSQL安装包2.3 创建MYSQL⽤户和⽤户组2.4 修改MYSQL⽬录的归属⽤户2.5 准备MYSQL的配置⽂件2.6 正式开始安装MYSQL2.7 复制启动脚本到资源⽬录2.8 设置MYSQL系统服务并开启⾃启2.9 启动MYSQL…

XoT:一种新的大语言模型的提示技术

这是微软在11月最新发布的一篇论文&#xff0c;题为“Everything of Thoughts: Defying the Law of Penrose Triangle for Thought Generation”&#xff0c;介绍了一种名为XOT的提示技术&#xff0c;它增强了像GPT-3和GPT-4这样的大型语言模型(llm)解决复杂问题的潜力。 当前提…

PHP原生类总结利用

再SPL介绍 SPL就是Standard PHP Library的缩写。据手册显示&#xff0c;SPL是用于解决典型问题(standard problems)的一组接口与类的集合。打开手册&#xff0c;正如上面的定义一样&#xff0c;有许多封装好的类。因为是要解决典型问题&#xff0c;免不了有一些处理文…

kr 第三阶段(九)64 位逆向

X64 汇编程序 64 位与 32 位的区别 更大的内存 64 位 CPU 与 32 位 CPU 的区别 引脚根数&#xff1a; x86 程序&#xff1a;20 根x64 程序&#xff1a;52 根&#xff0c;实际寻址的有 48 根&#xff0c;所以最大内存是 0~256T 寻址区间&#xff1a; x86 程序&#xff1a;0x0…

python实现一个简单的桌面倒计时小程序

本章内容主要是利用python制作一个简单的桌面倒计时程序&#xff0c;包含开始、重置 、设置功能。 目录 一、效果演示 二、程序代码 一、效果演示 二、程序代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ author: Roc-xb """import tkin…

汽车ECU的虚拟化技术初探(二)

目录 1.概述 2.U2A虚拟化方案概述 3.U2A的虚拟化功能概述 4.虚拟化辅助功能的使能 5.留坑 1.概述 在汽车ECU的虚拟化技术初探(一)-CSDN博客里&#xff0c;我们聊到虚拟化技术比较关键的就是vECU的虚拟地址翻译问题&#xff0c;例如Cortex-A77就使用MMU来进行虚实地址的转换…

阿里云国际站:专有宿主机

文章目录 一、专有宿主机的概念 二、专有宿主机的优势 三、专有宿主机的应用场景 一、专有宿主机的概念 专有宿主机&#xff08;Dedicated Host&#xff0c;简称DDH&#xff09;是阿里云专为企业用户定制优化的解决方案。具有物理资源独享、部署更灵活、配置更丰富、性价比…

遇到问题,我该如何提问?

作为IT行业的从业者&#xff0c;我们深知程序员在保障系统安全、数据防护以及网络稳定方面所起到的重要作用。他们是现代社会的护城河&#xff0c;用代码构筑着我们的未来。那程序员的护城河又是什么呢&#xff1f;是技术能力的深度&#xff1f;是对创新的追求&#xff1f;还是…

Linux yum,vim详解

yum是什么 yum是一个Linux系统预装的指令&#xff0c;yum的功能是可以对app进行搜索&#xff0c;下载&#xff0c;相当于Linux下的应用商店。 yum是读取Linux中镜像文件中的网页地址&#xff0c;下载用户所输入的命令。 如何使用yum下载软件 yum install -y(所有选项都yes) …

换根dp学习笔记

最近模拟赛经常做到&#xff0c;于是我就学习了一下。 算法原理 换根 d p dp dp的题一般都会给出一个无根树&#xff0c;因为以不同的点为根时&#xff0c;问题的答案不一样&#xff0c;所以它会让你输出答案的最大或最小值。 暴力去做这种题&#xff0c;就是以每个点为根然…

为什么要用“交叉熵”做损失函数

大家好啊&#xff0c;我是董董灿。 今天看一个在深度学习中很枯燥但很重要的概念——交叉熵损失函数。 作为一种损失函数&#xff0c;它的重要作用便是可以将“预测值”和“真实值(标签)”进行对比&#xff0c;从而输出 loss 值&#xff0c;直到 loss 值收敛&#xff0c;可以…

springboot项目使用Swagger3

一、Swagger介绍 号称世界上最流行的Api框架&#xff1b;Restful Api 文档在线自动生成工具>Api文档与API定义同步更新直接运行&#xff0c;可以在在线测试API 接口支持多种语言&#xff1a;&#xff08;java&#xff0c;Php…&#xff09; 二、Swagger3 准备工作 1、在p…

学习c#的第七天

目录 C# 封装 概念 Public 访问修饰符 Private 访问修饰符 Protected 访问修饰符 Internal 访问修饰符 Protected Internal 访问修饰符 总结 C# 封装 概念 在面向对象程序设计中&#xff0c;封装是一种将数据和方法包含在一个单元中&#xff0c;并控制对这些数据和方…

海康Visionmaster-Qt+VS 二次开发环境如何配置?

1 新建 Qt 工程&#xff0c;添加 Qt 模块 Core、GUI、Active Qt 和 Container Widgets 2 拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0\ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 3 第一…

AOMedia发布免版税沉浸音频规范IAMF

11月10日&#xff0c;开放媒体联盟&#xff08;AOMedia&#xff09;发布了旗下首个沉浸式音频规范IAMF&#xff08;https://aomediacodec.github.io/iamf/&#xff09;&#xff0c;IAMF是一种编解码器无关的容器规范&#xff0c;可以携带回放时间渲染算法和音频混音的信息&…

Spring Data JPA 实现集成实体对象数据库的创建、修改时间字段自动更新

JPA提供了一种事件监听器的机制&#xff0c;用于SQL审计&#xff0c;通过监听器我们可以很快速地去自动更新创建时间、修改时间&#xff0c;主要步骤如下&#xff1a; 一、创建基础实体&#xff0c;包含了创建和修改时间&#xff0c;然后让其他真正的实体继承该实体&#xff0…

59基于matlab的爬行动物搜索算法(Reptile search algorithm, RSA)

基于matlab的爬行动物搜索算法&#xff08;Reptile search algorithm, RSA&#xff09;一种新型智能优化算法。该算法主要模拟鳄鱼的捕食行为&#xff0c;来实现寻优求解&#xff0c;具有收敛速度快&#xff0c;寻优能力强的特点。程序已调通&#xff0c;可直接运行。 59matlab…

3分钟带你了解前端缓存-HTTP缓存

前情提要 前端缓存分为下面三大类&#xff0c;本文主要讲解HTTP缓存~ 1. HTTP缓存 强缓存协商缓存 2. 浏览器缓存 本地小容量缓存本地大容量缓存 3. 应用程序缓存 HTML5应用程序缓存 缓存作用 减少了冗余的数据传输减少服务器的负担提高了网站的性能加快加载网页速度 …

JPA Buddy快速创建update、find、count、delete、exists方法

JPA Buddy快速创建update、find、count、delete、exists方法&#xff0c;JPA默认提供的CrudRepository\JpaRepository提供的方法比较少&#xff0c;一般我们会手写一些方法&#xff0c;这里我们选择通过JPA Buddy快速生成&#xff0c;之前文章中讲到了JPA Buddy原本是IDEA收费插…

未来的拥塞控制与 Linux EEVDF 调度器

有破要有立。 前面提到 经典端到端拥塞控制将越来越失效&#xff0c;未来该如何&#xff0c;谈谈我的看法。 端到端拥塞控制的难点根本上是要解决公平性问题&#xff0c;顺带着提高资源利用率。我们很容易理解&#xff0c;在共享资源场景下&#xff0c;不公平一定是低效的&am…