Spring boot 自动装配原理

news2024/11/24 16:25:56

Spring boot 为了解决Bean的复杂配置,引入了自动装配机制;那么什么是自动装配,它的原理又是什么呢?我们先通过以下例子来了解以一下什么是自动装配。

Spring boot 集成 redis

  • 引入依赖包

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>2.6.0</version>
            </dependency>
    
  • 配置相关参数

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.database=0
    
  • controller

    @RestController
    @RequestMapping(value = "/redis")
    public class RedisController {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/save")
        public String save(@RequestParam String key, @RequestParam String value) {
            redisTemplate.opsForValue().set(key, value);
            return "ok";
        }
    
        @RequestMapping("/get")
        public String get(@RequestParam String key) {
            return (String) redisTemplate.opsForValue().get(key);
        }
    
    }
    

这是一个很简单的集成 redis 的案例,通过这个案例我们可以看出:RedisTemplate 这个类的 bean 对象,我们并没有通过 xml 的形式或者 注解 的形式注入到 ioc 容器中,但是我们可以直接通过 Autowired 注解自动从容器里面拿到相应的 bean 对象,从而进行属性的注入。

这就是Spring boot 中的自动装配,那这是怎么做到的呢?我们来分析一下它的原理,从而来理解RedisTemplate 是如何注入的。

自动装配原理

我们先来分析下上面案例的流程:

首先我们在集成的时候只做了一件事:引入依赖包;然后启动项目后,对象就自动进入到 IOC 容器中了。

那它是如何自动注入的呢?我们平常手动注入对象的时候,是通过 component 或者就是configuration中的bean注入。那它会不会也是呢?如果是这样,那按照理论来说应该也有一个componentscan来扫描对应路径,从而将对象注入;但是 spring boot 中可以引入各种各样的依赖,扫描的路径肯定也是千奇百怪的,那我们猜测应该是有一个固定的路径让其去扫描,从而能将依赖自动注入到容器中。

这个逻辑是不是有点眼熟,这不就是SPI机制吗?接下来我们从源码中逐步看下是如何实现的。

@SpringBootApplication注解是入口:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}

其中比较重要的是有以下三个注解:

  • @SpringBootConfiguration

    继承了 Configuration,表示当前是注解类

  • @EnableAutoConfiguration

    开启 spring boot 的注解功能,自动装配机制,借助 @import注解的功能。

  • @ComponentScan(excludeFilters = {})

    自动扫描并加载符合条件的组件(比如component,controller等),并将这些对象加载到 ioc 容器中。

    我们可以通过basepackages等属性来定制其自动扫描的范围,如果不指定,则默认会从声明。@ComponentScan所在类的 package 进行扫描,这就是为什么我们希望启动类放在根目录下的原因。

@EnableAutoConfiguration

这个注解我们看名字就是可以自动装配,显而易见就是最重要的那个了。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

其中最重要的两个注解如下:

  • @AutoConfigurationPackage
  • @Import({AutoConfigurationImportSelector.class})

AutoConfigurationPackage

我们先来看这个注解,看看这个注解做了那些事情:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

首先通过 import注解导入了Registrar类:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    // 注册当前启动类的根 package
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

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

我们可以这样理解它的作用:将添加该注解的类所在的package 作为自动配置package进行管理。就是我们上文所说的当Spring boot应用启动时会将启动类所在的package作为自动配置的package。

AutoConfigurationImportSelector

接下来我们来看@Import({AutoConfigurationImportSelector.class}),借助AutoConfigurationImportSelector,EnableAutoConfiguration可以帮助spring boot 项目将所有符合条件(Spring.factories)的bean定义都加载到当前IOC容器中。

该类最大的作用就是帮助我们找相关的配置类,而找配置类的过程就是我们上面所说的SPI机制。

关于SPI机制可以看这篇文章:SPI 机制详解

我们继续来看整体流程:首先看selectImports方法,该方法是找配置文件的入口。

为什么是这个方法呢?因为这个类继承了DeferredImportSelector接口,接口又继承了ImportSelector接口,所以在spring的流程中会进行调用。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    public AutoConfigurationImportSelector() {
    }

    // 找配置文件
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
}

然后进入getAutoConfigurationEntry方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 获取配置信息
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

继续往下,我们来看看它是如何获取配置信息的getCandidateConfigurations方法:

该方法主要是去加载各个组件jar下的 spring.factories文件

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "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;
}

看到SpringFactoriesLoader.loadFactoryNames是不是很眼熟?和我们SPI机制中的ServiceLoader.load是不是很类似?看来我们找对地方了,SpringFactoriesLoader的底层原理其实就是借鉴于JDK的SPI机制。

我们知道SPI机制都是从classpath下的service目录查找对应的文件?那么SpringFactoriesLoader从哪里查找呢?我们进入该类查看:

一进来我们就看到路径也被写死了,这样我们上面的问题就被解决了。

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap();
}

我们到spring boot 的自动装配jar中查看一下:

整个流程是不是很符合SPI机制,这样redis对象自动装配是不是能稍微想通了?

在这里插入图片描述

既然找到了文件,接下来肯定就是读取配置了,我们打断点来看下效果:

可以很清楚的看到spring.factories文件中的配置信息都读取到了。

在这里插入图片描述

我们来看下loadFactoryNames方法,它的入参为工厂类名称和对应的类加载器,方法会根据指定的类加载器,加载该类加载器搜索路径下的指定文件,即spring.factories文件。

传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类。

配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名:org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的KEY,获取对应的一组Configuration类。

总结

看过源码之后,我们对Spring boot 的装配原理进行一个简单的总结,方便我们记忆:

  • 首先 spring boot 启动

  • 扫描 @SpringBootApplication注解

  • 扫描@EnableAutoConfiguration注解

    这个注解里面带有一个@import注解,导入了AutoConfigurationImportSelector类,这个类实现了DeferredImportSelector接口,这个接口又实现了ImportSelector接口,在这个接口里面有一个叫做selectImports的方法,这个方法实现了配置类的寻找。

    整个寻找的过程就是Springboot 的SPI机制。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CTFShow pwn07 (ret2libc-64bit

用ROPgabdet 找到pop rdi地址和ret地址&#xff1a; 来自ctfshow pwn7&#xff1a; 64位程序是需要栈平衡的&#xff0c;而且前六个寄存器用完了才会用栈传参 %rdi&#xff0c;%rsi&#xff0c;%rdx&#xff0c;%rcx&#xff0c;%r8&#xff0c;%r9 用作函数参数&#xff0c;依…

rust编程-rust所有权理解(chapter 4.1)

目录 1. 什么是所有权 1.1 堆与栈 1.2 变量作用域 1.3 String类型 1.4 内存和分配 1.5 变量和数据交互的方式 1.5 所有权和函数 1.6 返回值和作用域 所有权 是Rust独有的特性&#xff0c;该设计开创了编程语言中的先河。所有权使得Rust能保证内存的安全&#xff0c;且不…

游程编码(Run Length Coding)

游程编码游程编码基本介绍示例1示例2游程编码适用的场景游程编码 游程编码&#xff08;Run Length Coding&#xff0c;简称RLC&#xff09;又称游程编码、行程长度编码、变动长度编码 等&#xff0c;是一种统计编码。主要技术是检测重复的比特或字符序列&#xff0c;并用它们的…

亚马逊云科技re:Invent 2022 Ruba Borno主题演讲

2022亚马逊云科技re:Invent全球大会精彩内容应接不暇&#xff0c;亚马逊云科技全球渠道与联盟副总裁Ruba Borno在2022亚马逊云科技re:Invent大会的全球合作伙伴峰会上&#xff0c;为合作伙伴带来一系列全新的合作伙伴创新服务。 云上发展持续加速的当下&#xff0c;上云好比一场…

使用Fiddler对手机App抓包

目录 一、查看 Fiddler 的 ip 地址 二、设置 Fiddler 允许远程连接 三、进行手机端 App 的抓包 3.1.准备工作 3.2.手机设置 3.3.安装根证书 四、可能会遇到的问题 一、查看 Fiddler 的 ip 地址 有两种方法都可以查询到。 第一种方法&#xff1a; 打开 Fiddler&#xff…

[附源码]计算机毕业设计springboot在线招聘网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

一分钟带你了解音视频开发进阶(先收藏了)

FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发学习路线 随着基础设施的完善&#xff08;光纤入户、wifi覆盖、5G普及&#xff09;的影响&#xff0c;将短视频、直播、视频会议、在线教育、在线医疗瞬间推到了顶峰&#xff0c;人们对音视频的需求和要求也越来越强烈 音视…

实现java项目idea打包发布至服务器(完整版)

问题&#xff1a;如何快速部署本地代码到服务器&#xff1f; 今天介绍的是使用idea的一款插件(Alibaba Cloud Toolkit)实现&#xff0c;首先需要在自己的服务器上安装运行环境&#xff0c;包括&#xff1a;jdk、maven、mysql等&#xff0c;这些操作就不详细说了&#xff0c;可…

Inductive Representation Learning on Large Graphs 论文/GraphSAGE学习笔记

1 动机 1.1 过去的方法 现存的方法大多是transductive的&#xff0c;也就是说&#xff0c;在训练图的时候需要将整个图都作为输入&#xff0c;为图上全部节点生成嵌入&#xff0c;每个节点在训练的过程中都是可知的。举个例子&#xff0c;上一次我学习了GCN模型&#xff0c;它…

考研数据结构大题整合_组三(LZH组)

考研数据结构大题整合 目录考研数据结构大题整合三、LZH组LZH 组一LZH 组二LZH 组三LZH 组四LZH 组五LZH 组七三、LZH组 LZH 组一 给出如图所示的无向图G的邻接矩阵和邻接表两种存储结构. &#xff08;2&#xff09;解答下面的问题&#xff08;6分&#xff09; &#xff08;…

二、进程管理(五)死锁

目录 5.1死锁的定义和产生条件 5.2死锁的处理策略 5.2.1死锁预防 5.2.2死锁避免 5.2.3死锁检测和解除 5.1死锁的定义和产生条件 在并发环境下&#xff0c;各进程因竞争资源而造成的一种互相等待对方手里的资源&#xff0c;导致各进程都阻塞&#xff0c;都无法向前推进的现…

【GlobalMapper精品教程】034:创建漫游动画并制作漫游视频的方法

本实例讲解在globalmapper中根据路径创建漫游动画,并制作漫游视频的方法。 文章目录 一、绘制漫游路径二、创建3D虚拟漫游三、播放虚拟漫游四、保存虚拟漫游实验数据可以是点云数据、DEM、三维模型等,本文加载数字表面模型DSM进行演示。 一、绘制漫游路径 同创建矢量线状数据…

海带软件分享——日常办公学习软件分享(收藏)

>>>深度学习Tricks&#xff0c;第一时间送达<<< &#x1f680; 写在前面 &#x1f431;‍&#x1f3cd; 本期开始&#xff0c;小海带会定期推荐一些日常办公学习软件及趣味网址&#xff0c;供大家交流参考 ~ 小伙伴们记得一键三连喔&#xff01;&#x1f6…

几款好用到爆炸的在线画图工具

前言 实际工作中&#xff0c;我们经常会编写文档以及制作图表。尤其是对一名优秀的攻城狮来说&#xff0c;经常会用各种各样的软件来制作流程、思维导图、思维笔记等。一个良好的思维导图能系统概括项目工程的整体结构和开发的系统框架。要想制作一个完美的流程图、思维导图离不…

菜狗杯Misc一层一层一层地剥开我的♥wp

目录一、原题二、解题步骤对jpg图片的处理对文件名是一个心形的数据文件的处理base100解码这题完全是看着官方wp复现的&#xff0c;感觉涉及的步骤比较多但每一步本身不难&#xff0c;多记录一遍加深印象。 一、原题 原题给的是一个叫myheart.zip的文件&#xff0c;但尝试解压…

高通开发系列 - ALSA声卡驱动中音频通路kcontrol控件

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 高通开发系列 - ALSA声卡驱动中音频通路kcontrol控件问题背景高通音频通路如何建立widget和routemixer类控件名组合mixer类控件名拼接…

CRM客户关系管理系统(含源码+论文+答辩PPT等)

该项目采用技术&#xff1a;JSP Servlet MySQLjdbccssjs等相关技术&#xff0c;项目含有源码、文档、配套开发软件、软件安装教程、项目发布教程等 项目功能介绍&#xff1a; 系统管理&#xff1a;用户登录退出、个人资料修改 客户管理&#xff1a;客户信息管理、客户来源、联系…

Softmax回归——动手学深度学习笔记

Softmax回归&#xff0c;虽然它的名称叫做回归&#xff0c;其实它是一个分类问题。 回归VS分类 回归估计一个连续值 如&#xff1a;回归估计下个月的房价 分类预测一个离散类别 如&#xff1a; &#xff08;1&#xff09;MNIST&#xff1a;手写数字识别&#xff08;10类&…

初识springmvc

狂神的servlet回顾就不在这里写了。可以翻之前的笔记。 原生开发&#xff1a; 创建webapp的maven项目。 也就是四个文件 &#xff08;不用思考里面的代码&#xff0c;直接CV先走一遍流程&#xff09; HelloController&#xff1a; package com.Li.controller;import org.sp…

SecureCRT之Xmodem操作步骤

以锐捷S3760为例&#xff1a; 故障现象&#xff1a;s3760无法加载&#xff0c;需要重刷RGOS。 一、使用控制线连接s3760&#xff0c;开机加载引导&#xff0c;按Ctrl_B进入“BOOT MENU”页面&#xff1a; 选择【0】进入XModem操作界面&#xff1a; 说明&#xff1a; 0--更新…