前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot自动装配源码笔记

SpringBoot自动装配源码笔记

作者头像
doper
发布2022-09-26 17:45:51
2600
发布2022-09-26 17:45:51
举报
文章被收录于专栏:后台技术杂项笔记

SpringBoot自动装配笔记

个人学习整理

参考的原内容来自b站雷神SpringBoot视频和相关笔记

https://www.bilibili.com/video/BV19K4y1L7MT

https://www.yuque.com/atguigu/springboot/qb7hy2#Lv0Fg

1. 前置知识

1.1. 条件装配@Conditional

正如其名称一样,当组件满足了@Conditional给定的条件时才会进行装配到容器。

image-20220116095203015
image-20220116095203015

1.1.1 @ConditionalOnBean

@ConditionalOnBean注解的作用是当注解参数对应的bean不存在时,则其标注的Bean也不会被注册。

具体示例:

首先创建一个SpringBoot项目,加入Spring Web依赖,然后创建对应的类,具体目录如下(红色框为修改或添加部分):

image-20220116100256933
image-20220116100256933
  • com.doper.pojo.User.java
代码语言:javascript
复制
package com.doper.pojo;

public class User {
    private String name;

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

    public User() {
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • com.doper.pojo.Book.java
代码语言:javascript
复制
package com.doper.pojo;

public class Book {
    private String name;

    public Book() {
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Book{" +
                "name=" + name +
                '}';
    }
}
  • com.doper.config.Config.java
代码语言:javascript
复制
package com.doper.config;

import com.doper.pojo.Book;
import com.doper.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean("user")
    public User user() {
        return new User("法外狂徒张三");
    }

    @Bean("book")
    public Book book() {
        return new Book("法外狂徒的自我修养");
    }
}
  • com.doper.ConditionTestApplication
代码语言:javascript
复制
package com.doper;

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

@SpringBootApplication
public class ConditionalTestApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(ConditionalTestApplication.class, args);
        boolean haveUser = run.containsBean("user");	//容器内是否注册了user Bean
        boolean haveBook = run.containsBean("book");	//容器内是否注册了book Bean
        System.out.println("容器是否有user bean? : " + haveUser);
        System.out.println("容器是否有book bean? : " + haveBook);
    }
}

运行项目,控制台输出结果:

image-20220116100624910
image-20220116100624910

可以看到容器内确实注册了user和book

下一步修改Config文件

image-20220116100849683
image-20220116100849683

如图所示,注释user()方法的@Bean注解,此时它只是Config类下的一个普通方法,并且在book()方法上面增加@ConditionalOnBean(name = "user")。此时再运行一下项目

image-20220116101145232
image-20220116101145232

此时发现user bean不存在(因为@Bean注解已经被注释掉了),并且book bean也不存在(@ConditionalOnBean注解的作用)

扩展: 使用@ConditionalOnBean时要注意bean的声明顺序

我们继续改变Config,如下:

image-20220116101631562
image-20220116101631562

我们恢复user()上的@Bean注解,此时运行项目,结果如下:

image-20220116101709775
image-20220116101709775

两个bean都可以顺利被注册。

但这是如果调换一下两个bean的声明顺序,如下

image-20220116101746002
image-20220116101746002

此时得到如下结果:

image-20220116101803512
image-20220116101803512

可以看到book bean不能被创建,因为它是从上往下顺序注册到容器的的,在注册book时user bean还没被注册,@ConditionalOnBean条件不成立,因为book bean不会被注册。

1.1.2 @ConditionalOnMissingBean

作用和@ConditionalOnBean相反

1.2. 配置绑定

1.2.1. 方式一: @ConfigurationProperties

首先在1.1中的项目增加如下类和代码(红色部分为增加或者修改)

image-20220116103722250
image-20220116103722250
  • com.doper.config.Config.java
代码语言:javascript
复制
// 增加如下bean,这里直接在Student类上增加@Compoent注解也可以
@Bean("student")
public Student student() {
    return new Student();
}
  • com.doper.pojo.Student.java
代码语言:javascript
复制
package com.doper.pojo;

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

//使用该注解开启配置绑定,配置的属性前缀为student
@ConfigurationProperties(prefix = "student")
public class Student {
    private int id;
    private String name;

    public Student() {
    }

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
  • com.doper.ConditionTestApplication
image-20220116103544933
image-20220116103544933
  • application.properties

使用yaml格式的配置也行

代码语言:javascript
复制
student.id=1
student.name=张三

运行项目,结果如下:

image-20220116103833818
image-20220116103833818

配置文件中的属性已经被自动配置到容器的bean里面了。

如果中文乱码了,原因是编码格式不支持,在idea可以通过打开如下配置解决

image-20220116103936721
image-20220116103936721

1.2.2 方式二: @EnableConfigurationProperties

这种情况适用于@Autowired自动注入的情况,但本身容器内并不会注册这个bean。

  • 把Config类中的这部分代码注释
image-20220116104932943
image-20220116104932943
  • 在Config顶部增加注解@EnableConfigurationProperties(Student.class)
image-20220116105002712
image-20220116105002712
  • 创建控制器验证Autowired
image-20220116105030569
image-20220116105030569
  • 修改启动方法,验证是否注册了bean
image-20220116105113843
image-20220116105113843

启动项目,得到如下结果

浏览器:

image-20220116105151639
image-20220116105151639

控制台:

image-20220116105205597
image-20220116105205597

结果可见控制器的@Autowired已经生效完成自动注入,但容器内并不存在这个bean.

2. 自动装配原理

在在了解前置知识中几个注解的作用后就可以来看SpringBoot自动装配的原理了,这里由于知识有限,只能结合视频及笔记列个大概。

2.1. @SpringBootApplication

点开启动类的@SpringBootApplication注解,可以发现它由如下注解组成

代码语言:javascript
复制
@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 {
	//...
}

这里前面4个为元注解

  • @Target:描述注解的范围,即注解在哪里可以使用。
  • @Retention: 描述注解的生命周期,即其保留的时间长短。这里为RUNTIME即运行时有效
  • @Documented: 生成doc文档时保留其他注解,用于辅助说明
  • @Inherited: 注释类型会被子类自动继承。

然而自动装配的重点为如下三个注解

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

2.2. @SpringBootConfiguration

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    //...
}
  • @Configuration 代表这是一个配置类,说明启动类是一个核心的配置类。
  • @Indexed 为Spring的模式注解添加索引,提升应用启动性能。具体见这里

2.3. @ComponentScan

包扫描注解

2.4. @EnableAutoConfiguration

自动装配的关键

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    //...
}

除去上面4个元注解,现在逐层看@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

2.4.1 @AutoConfigurationPackage

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
    //...
}

这里重点在@Import(AutoConfigurationPackages.Registrar.class)

@Import的作用是为容器导入组件,这里导入了AutoConfigurationPackages.Registrar类,打开AutoConfigurationPackages.Registrar查看

代码语言:javascript
复制
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //将某个包的所有组件进行注册
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }

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

}

在register打断点,调试,查看相关信息

image-20220116144135943
image-20220116144135943

这里可以看到new PackageImports(metadata).getPackageNames()的值为com.doper,即我们主启动类所在的包。所以这里就将我们包下的组件导入。继续进入。

image-20220116144638117
image-20220116144638117

这里可以看到我们的com.doper包也被注册进入。

代码语言:javascript
复制
/**
	 * Register a new bean definition with this registry.
	 * Must support RootBeanDefinition and ChildBeanDefinition.
	 ...
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException;

2.4.2. @Import(AutoConfigurationImportSelector.class)

点进AutoConfigurationImportSelector类,看到如下方法

image-20220116145118190
image-20220116145118190

这里规定了要导入哪些包,并把包名String保存到Stirng数组然后返回,而这些包名String都由getAutoConfigurationEntry方法获得。点进该方法查看

image-20220116145622911
image-20220116145622911

这里关注红色框这一行,它通过getCandidateConfigurations方法获得所要导入的包名,继续查看

image-20220116150714406
image-20220116150714406

如注释所写,这个方法就是通过SpringFactoriesLoader类来返回需要自动装配的类名,继续查看SpringFactoriesLoaderloadFactoryNames方法

image-20220116151057621
image-20220116151057621
image-20220116151130110
image-20220116151130110

直接看loadSpringFactories方法,里面在getResources方法通过加载所有路径为META-INT/spring.factories中的资源来得到要加载的类名。如

spring-boot-autoconfigure-2.6.2.jar包下的META-INT/spring.factories文件,里面列出了需要自动装配的类的类名。

image-20220116151423200
image-20220116151423200

接下来打点验证是否加载了这个配置文件下的类

image-20220116151640987
image-20220116151640987

可以看到控制台中要自动装配的类名数组确实和配置文件中的配置项是对应的。并且后缀都是XXXAutoConfigutation

但要注意的是,这里面列出的配置项虽然已经全被加载进来,但是不会全部配置,实际上它是按需配置的,这里就涉及到前置知识中的条件装配。

这里打开CacheAutoConfiguration类查看

image-20220116152057647
image-20220116152057647

可以看到这里使用了@ConditionalOnClass@ConditionalOnBean@ConditionalOnMissingBean条件装配注解。这里报红是因为我们没有导入相关的类,因此在实际自动装配阶段该bean就不会被自动装配。

2.4.3 修改默认配置

在底层中虽然它是自动装配,但是我们也可以刚过修改配置文件application.properties配置项来进行调整,这里以DispatcherServletAutoConfiguration为例

image-20220116153443148
image-20220116153443148
image-20220116153459761
image-20220116153459761

通过上述1.1.2节的例子,可以看到这里DispatcherServlet的属性都是由配置类来决定的。若在配置文件中有配置项,则会在载入时去加载这些属性到XXXXProperties中,然后再取出其相关属性然后设置。

2.4.4 总结

@EnableAutoConfiguration注解的作用由以下两个注解决定

  • @AutoConfigurationPackage: 自动扫描我们启动类所在的包(例子中为com.doper)
  • @Import(AutoConfigurationImportSelector.class): 加载所有路径为META-INT/spring.factories的资源,加载所有需要自动装配的类名。但是在条件装配注解的作用下,并不会去配置所有的组件中。并且我们还可以通过application.properties配置文件来配置我们的组件属性。

3. 注解关系图

image-20220116160104756
image-20220116160104756

4. 自动装配总结

(以下总结来自链接笔记)

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。
  • xxxxxAutoConfiguration —-> 组件 —-> xxxxProperties里面拿值 ——> application.propertie
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot自动装配笔记
  • 1. 前置知识
    • 1.1. 条件装配@Conditional
      • 1.1.1 @ConditionalOnBean
      • 1.1.2 @ConditionalOnMissingBean
    • 1.2. 配置绑定
      • 1.2.1. 方式一: @ConfigurationProperties
      • 1.2.2 方式二: @EnableConfigurationProperties
  • 2. 自动装配原理
    • 2.1. @SpringBootApplication
      • 2.2. @SpringBootConfiguration
        • 2.3. @ComponentScan
          • 2.4. @EnableAutoConfiguration
            • 2.4.1 @AutoConfigurationPackage
            • 2.4.2. @Import(AutoConfigurationImportSelector.class)
            • 2.4.3 修改默认配置
            • 2.4.4 总结
        • 3. 注解关系图
        • 4. 自动装配总结
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档