当前位置: 首页 > article >正文

(2)SpringBoot自动装配原理简介

SpringBoot自动装配

image-20250129011450796

这里写目录标题

  • SpringBoot自动装配
    • 启动器
    • 主程序
      • 自定义扫描包
      • @SpringBootApplication
        • @SpringBootConfiguration
        • @EnableAutoConfiguration
          • @AutoConfigurationPackage
          • @Import({AutoConfigurationImportSelector.class})选择器
            • AutoConfigurationEntry
            • getCandidateConfigurations
    • 组件添加
      • 代理设置
      • @import注解
      • Conditional注解
      • @ImportResource注解
    • 配置绑定
      • @ConfigurationProperties注解
      • @EnableConfigurationProperties注解
    • 主函数
      • @EnableAutoConfiguration注解
        • @AutoConfigurationPackage注解
        • @Import({AutoConfigurationImportSelector.class})
      • 按需开启
    • 自动装配流程
      • 条件装配流程
      • 修改默认配置

我们通过观察创建的SpringBoot项目的pom.xml文件发现他的核心依赖大部分在他的父工程中,长按ctrl并点击父项目就可以进入到父项目的文件,从浏览器上面下载压缩包的那种创建springBoot方式不能查看夫项目的文件。

spring-boot-dependencies是parent的父项目。

image-20250126232001767

在这里制定了大量的依赖版本,还有过滤器。

我们的pom.xml文件与之前不同的就是我们在引入依赖的时候可以不指定依赖的版本,这就是因为在父工程中指定了依赖的版本,我们没必要再写。当然,如果要引入他没有规定的依赖还是要声明版本的。

如果我们对他的依赖版本不满意,想要使用我们自己的,我们可以指定需要的版本

image-20250127092614695

这个就是修改mysql版本的方法。

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

项目自带的启动器,下面有很多的在他的基础上添加后缀的依赖,比如说-web配置内置的tomcat容器等

访问下面这个网址可以找到对于启动器的介绍

Build Systems :: Spring Boot

image-20250127093321786

image-20250126233141282

这里可以查询到我常用的依赖。如aop,缓存等的,我们也可以使用idea的分析依赖树功能,更好的查看我们项目中的依赖。

image-20250127093608520

主程序

首先alt+回车查看他的返回类型ConfigurableApplicationContext是一个IOC容器,我们通过getBeanDefinitionNames来打印一下它里面的所有组件。

package com.kang.start;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StartApplication {

    public static void main(String[] args) {
        //ConfigurableApplicationContext是一个IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
        String[] names = run.getBeanDefinitionNames();
        for (String name:names) {
            System.out.println(name);
        }
    }
}

image-20250127095933200

说明他配置了dispathcer,我们之前学习的基本上所有的组件都可以在这里找到。

自定义扫描包

注意在官网上说的包结构的安排,只有把组件放在项目目录下才能扫描到。

image-20250127150206230

当然想要改变扫描的包也很简单,加一个这个

@SpringBootApplication(scanBasePackages = "com.kang")

image-20250127150607182

当然也有第二种方法就是直接把它里面的注解拽出来,这样配置就不会与SpringBootApplication标签里面的包扫描冲突

/*@SpringBootApplication*/

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.kang")
public class StartApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
        String[] names = run.getBeanDefinitionNames();
        for (String name:names) {
            System.out.println(name);
        }
    }
}

这里我们也可以得出一个结论

@SpringBootApplication
等价于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.kang")

在 [application.properties] 文件中我们发现,我们点开配置文件的源码以后,里面绑定的都是类,最后这些类都会注入到IOC容器中。

@SpringBootApplication

他的主程序很少,其中最关键的注解就是@SpringBootApplication声明它是springBoot程序,查看他的源文件会发现两个最重要的注解

@SpringBootConfiguration
@SpringBootConfiguration
	@Configuration
		@Component//这个注解代表他本质上还是一个spring的组件
@EnableAutoConfiguration
@EnableAutoConfiguration//自动导入配置文件;
	@AutoConfigurationPackage//自动配置包
	@Import({AutoConfigurationImportSelector.class})//导入选择器
@AutoConfigurationPackage
@Import({Registrar.class})

image-20250128235521109

通过Regist类我们可以发现的本质就是一个自动注册包的类

@Import({AutoConfigurationImportSelector.class})选择器

这两个配置注解在AutoConfigurationImportSelector这个类中有一个核心语句就是获取所有的配置

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
AutoConfigurationEntry

AutoConfigurationEntry获得自动配置的实体

image-20250129001747171

getCandidateConfigurations

了解一下getCandidateConfigurations的

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
    List<String> configurations = importCandidates.getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

其中 "No auto configuration classes found in META-INF/spring/"这里说的就是自动配置的核心文件

image-20250127152152077

就是它,点开我们可以看到很多的配置,比如说常见的aop,jdbc等的配置。但是我们点开以后发现很多的都是爆红的,这说明我们并没有配置该组件,我们只要在pom文件中添加依赖,下载完成以后就不爆红了。

image-20250129011450796

组件添加

首先创建实体类

User类

package com.kang.start.pojo;

public class User {
    String name;
    int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Pet类

package com.kang.start.pojo;

public class Pet {
    String name;

    public Pet() {
    }

    @Override
    public String toString() {
        return "Pet{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Pet(String name) {
        this.name = name;
    }
}

然后注册到容器中的方法有两种

方法一:
配置beans.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user01" class="com.kang.start.pojo.User">
        <property name="age" value="18"/>
        <property name="name" value="zhangsan"/>
    </bean>
    <bean id="Tomcat" class="com.kang.start.pojo.Pet">
        <property name="name" value="Tomcat"/>
    </bean>
</beans>

方法二:

使用配置类(配置类本身也是组件)+注解

  1. @Configuration//声明配置类==xml文件
  2. @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
  3. @Bean(“Tom”)//自定义名称
package com.kang.start.config;

import com.kang.start.pojo.Pet;
import com.kang.start.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration//声明配置类==xml文件
public class myConfig {
    @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
    public User user01(){
        return new User("xiaom",18);
    }
    @Bean("Tom")//自定义名称
    public Pet TomcatPet(){
        return new Pet("Tomcat");
    }
}

我们在主函数中输出一下

public class StartApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
        String[] names = run.getBeanDefinitionNames();
        for (String name:names) {
            System.out.println(name);
        }
    }
}

image-20250127154604459

得到了我们注册的两个类

代理设置

@Configuration(proxyBeanMethods = true)

proxyBeanMethods参数是设置代理,当他默认值为true的时候,当我们创建两个对象的时候,他们的哈希地址是一样的,设置为false的时候就是不同的。

这就涉及到两种模式:

  1. Full:proxyBeanMethods = true,把创建的对象放在容器中,调用的时候从容器中取出来
  2. Lite:proxyBeanMethods = false,每次调用的时候创建对象。(轻量级模式)

举个例子:如果我们在User对象中绑定一个Pet类型的cat,当我们代理为true的时候,我们绑定的宠物就是容器中的cat,这就成功的让User绑定到了cat,但是如果代理设置为false,那么我们绑定的Pet就是新创建的,并不是我们在配置类中绑定的那个Pet。

总的来说,当我放入IOC的对象存在IOC依赖的时候就需要打开代理,当我们没有IOC依赖的时候就调成false,让我们更快的加载。因为他跳过了在IOC中检测的步骤。

@import注解

这个组件可以帮助我们在IOC容器中创建指定类型的组件

比如说,我们随便import一个组件,是自定义的

@Import({User.class})
@Configuration(proxyBeanMethods = true)
public class myConfig {
    @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
    public User user01(){
        return new User("xiaom",18);
    }
    @Bean("Tom")//自定义名称
    public Pet TomcatPet(){
        return new Pet("Tomcat");
    }
}

输出一下

public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
        String[] names = run.getBeanNamesForType(User.class);
        for (String name:names) {
            System.out.println(name);
        }
    }

image-20250127171713282

他的名字就是类地址名。这就代表成功注册了

Conditional注解

他的作用是在指定条件下注册指定的类,看下他的源文件

image-20250127172306288

ctrl+H,我们发现他有很多很多的派生注解。

image-20250127173001574

首先解释一个方法containsBean,它可以检测容器中是否存在指定的类

我们先把tom组件注释掉

public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(StartApplication.class, args);
        boolean tom = run.containsBean("tom");
        System.out.println("输出是否存在组件tom:"+tom);
        boolean user01 = run.containsBean("user01");
        System.out.println("输出是否存在组件user01:"+user01);
    }

image-20250127173638853

我们首先对User和Pet建立依赖

package com.kang.start.pojo;

public class User {
    String name;
    int age;
    Pet pet;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Pet getPet(Pet pet) {
        return this.pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
@Configuration(proxyBeanMethods = true)
public class myConfig {
    @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
    public User user01(){
        User user = new User("xiaom", 18);
        user.setPet(TomcatPet());
        return user;
    }
    @Bean("Tom")//自定义名称
    public Pet TomcatPet(){
        return new Pet("Tomcat");
    }
}

使用一个注解@ConditionalOnBean(name = “Tom”)

@Configuration(proxyBeanMethods = true)
public class myConfig {
    @Bean("Tom")//自定义名称
    public Pet TomcatPet(){
        return new Pet("Tomcat");
    }
    @ConditionalOnBean(name = "Tom")//检测容器中是否存在Tom,如果存在则创建user01
    @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
    public User user01(){
        User user = new User("xiaom", 18);
        user.setPet(TomcatPet());
        return user;
    }
}

检测是否存在

image-20250127181448945

当我们注释掉Tom时

image-20250127181529172

user01也不会注册;也可以把它注解在整个类以外,如果存在这个类,下面所有的配置才会生效。

@ImportResource注解

它可以帮助我们自动导入beans.xml文件,比如说,在我们运行一个比较老的项目的时候他的配置用的全都是xml文件注册的bean,我们想要快速的把他的xml文件中的beans注册到我们的IOC容器中,我们就可以使用在各个注解。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="hehe" class="com.kang.start.pojo.Pet">
        <property name="name" value="Tomcat"/>
    </bean>
    <bean id="haha" class="com.kang.start.pojo.User">
        <property name="age" value="18"/>
        <property name="name" value="zhangsan"/>
    </bean>
</beans>

这样一个xml文件

@Configuration(proxyBeanMethods = true)
@ImportResource("classpath:beans.xml")//快速引入指定的bean注册文件
public class myConfig {
    @Bean("Tom")//自定义名称
    public Pet TomcatPet(){
        return new Pet("Tomcat");
    }
    @ConditionalOnBean(name = "Tom")
    @Bean//添加组件,方法名就是id,返回类型就是组件类型,返回值就是对象。
    public User user01(){
        User user = new User("xiaom", 18);
        user.setPet(TomcatPet());
        return user;
    }
}

image-20250127182738665

配置绑定

@ConfigurationProperties注解

简单来说就是使用Java读取到propertie文件中的内容,把它封装到JavaBean中,方便使用。

传统的方法就是使用Properties类读取配置文件的Key和value,遍历它们,然后一一对应的封装到JavaBean中。

这时可以使用一个注解@ConfigurationProperties(prefix = “(前缀)”)

prefix是前缀

具体的使用步骤:

首先新建Car类,为他加入get/set方法还有tostring方法,有参无参构造方法等

使用@Component注解来注册到IOC容器中

@ConfigurationProperties(prefix = “mycar”)//读取properties文件中前缀为mycar的方法

注意:properties文件中的前缀必须全小写,不能使用驼峰写法

package com.kang.start.pojo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    public String brand;
    public Integer pirce;

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", pirce=" + pirce +
                '}';
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPirce() {
        return pirce;
    }

    public void setPirce(Integer pirce) {
        this.pirce = pirce;
    }

    public Car() {
    }

    public Car(String brand, Integer pirce) {
        this.brand = brand;
        this.pirce = pirce;
    }
}

properties文件:

mycar.brand=baoma
mycar.pirce=10000

在Controller类中用@Autowried注解来吧IOC容器中的car类拽下来

@RestController
public class myConfig {
    @Autowired
    Car car;
    @RequestMapping("/car")
    public Car car(){
        return car;
    }
}

访问这个页面

image-20250128092016762

@EnableConfigurationProperties注解

当然,我们还要考虑一种情况,就是这个组件如果不是我们自定义的我们要把它注册到IOC容器中怎么办呐?我们也不能在人家的源文件中添加@Component注解

这个时候,我们可以在配置类当中使用这个注解

@EnableConfigurationProperties(类名.class)

代替@Component注解;

主函数

@EnableAutoConfiguration注解

查看他的源文件里面有两个核心的注解

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
@AutoConfigurationPackage注解

他的源码:

@Import({Registrar.class})//给容器导入组件
public @interface AutoConfigurationPackage {

其中导入组件的时候用的是Registrar来导入一系列组件

image-20250128094347691

所以说@EnableAutoConfiguration注解的一个作用就是导入指定包下的所有组件

具体来说就是main程序所在包下的所有组件

@Import({AutoConfigurationImportSelector.class})

@EnableAutoConfiguration还有一个重要的注解

@Import({AutoConfigurationImportSelector.class})

这个注解,先查看AutoConfigurationImportSelector这个方法,他是一个批处理方法。

其中有一个selectImports。

image-20250128095154097

研究一下getAutoConfigurationEntry(annotationMetadata)这个方法

他就是给IOC容器中批量导入组件,我们来Debug一下

image-20250128095934198

getAutoConfigurationEntry(annotationMetadata)这个方法就是调用

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

这个方法,获取所有要导入容器中的类。

此外还有一个重要的工厂方法;

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

利用工厂方法加载。

SpringFactoriesLoader方法

image-20250128213934438

默认他会扫描这个位置META-INF/spring.factories

在我们的自动装配核心类包spring-boot-autoconfigure-3.4.2.jar中

image-20250128214343134

说明这个文件里面声明了启动springBoot时要注册的所有配置类

按需开启

虽然所有的配置文件会全部配置,但是真正使用的时候只会按需配置。

比如说我们随便打开一个包

image-20250128223723179

条件配置,只有配置了指定的类才会继续下面的配置。最终会按照条件配置规则,按需装配

自动装配流程

条件装配流程

首先阅读一下aop类的源码

image-20250128224845584

image-20250128225040717

由于这个配置要求有Advice类,所以暂且将其称之为复杂配置。

image-20250128225251756

将这个要求不高的称之为简单配置。下面的ClassProxyingConfiguration就是对aop的配置。

修改默认配置

看一下dipatcherServlet的配置类DispatcherServletAutoConfiguration

位置:org\springframework\boot\autoconfigure\web\servlet\DispatcherServletAutoConfiguration.class

最下面有一个有趣的方法,文件上传解析器

@Bean
@ConditionalOnBean({MultipartResolver.class})//检测容器中是否有MultipartResolver类
@ConditionalOnMissingBean(
    name = {"multipartResolver"}//如果这个类的名字不是multipartResolver
)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    return resolver;//修正并返回正确的解析器
}

http://www.kler.cn/a/524632.html

相关文章:

  • 【python】subprocess.Popen执行adb shell指令进入linux系统后连续使用指令,出现cmd窗口阻塞问题
  • 17.Word:李楠-学术期刊❗【29】
  • selenium自动化测试框架——面试题整理
  • Visual Studio Code修改terminal字体
  • 【go语言】结构体
  • 【教学类-89-01】20250127新年篇01—— 蛇年红包(WORD模版)
  • CSS语言的区块链
  • Vue 3 30天精进之旅:Day 08 - 组件通信
  • 锁升级过程与优化操作
  • 消息队列篇--通信协议篇--STOMP(STOMP特点、格式及示例,WebSocket上使用STOMP,消息队列上使用STOMP等)
  • 大屏 UI 设计风格的未来趋势
  • FreeRTOS从入门到精通 第十四章(队列集)
  • [NOI1995] 石子合并
  • Antd React Form使用Radio嵌套多个Select和Input的处理
  • 架构技能(六):软件设计(下)
  • 用户创建命令的详细使用与参数说明
  • 深度学习每周学习总结R5(LSTM-实现糖尿病探索与预测-模型优化)
  • Origami Agents:通过AI驱动的研究工具提升B2B销售效率
  • 网络工程师 (8)存储管理
  • docker运行Open-Webui 界面化展示 deepseek-r1大模型
  • 【新春特辑】2025年春节技术展望:蛇年里的科技创新与趋势预测
  • CUDA学习-内存访问
  • 飞鸟小目标检测数据集VOC+YOLO格式1657张2类别
  • 解锁豆瓣高清海报:深度爬虫与requests进阶之路
  • Kubernetes 环境中的自动化运维实战指南
  • 【灵蛇献瑞】| 2024 中国开源年度报告正式发布!