SpringBoot bean自动装配原理,这一篇就够了! 1

简介: SpringBoot bean自动装配原理,这一篇就够了!

前言

学习SpringBoot,绝对避不开自动装配这个概念,这也是SpringBoot的关键之一

本人也是SpringBoot的初学者,下面的一些总结都是结合个人理解和实践得出的,如果有错误或者疏漏,请一定一定一定(不是欢迎,是一定)帮我指出,在评论区回复即可,一起学习!

篇幅较长,希望你可以有耐心.

如果只关心SpringBoot装配过程,可以直接跳到第7部分

想要理解spring自动装配,需要明确两个含义:

  • 装配,装配什么?
  • 自动,怎么自动?

1. Warm up

在开始之前,让我们先来看点简单的开胃菜:spring中bean注入的三种形式

首先我们先来一个Person类,这里为了篇幅长度考虑使用了lombok

如果你不知道lombok是什么,那就最好不要知道,加了几个注解之后我的pojo类Person就完成了

/**
 * @author dzzhyk
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private Boolean sex;
}

在Spring中(不是Spring Boot),要实现bean的注入,我们有3种注入方式:

1.1 setter注入

这是最基本的注入方式

首先我们创建applicationContext.xml文件,在里面加入:

<!-- 手动配置bean对象 -->
<bean id="person" class="pojo.Person">
    <property name="name" value="dzzhyk"/>
    <property name="age" value="20"/>
    <property name="sex" value="true"/>
</bean>

这里使用property为bean对象赋值

紧接着我们会在test包下写一个version1.TestVersion1类

/**
 * 第一种bean注入实现方式 - 在xml文件中直接配置属性
 */
public class TestVersion1 {
    @Test
    public void test(){
        ApplicationContext ca = new   ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = ca.getBean("person", Person.class);
        System.out.println(person);
    }
}

这里我使用了ClassPathXmlApplicationContext来加载spring配置文件并且读取其中定义的bean,然后使用getBean方法使用id和类来获取这个Person的Bean对象,结果成功输出:


Person(name=dzzhyk, age=20, sex=true)

1.2 构造器注入

接下来是使用构造器注入,我们需要更改applicationContext.xml文件中的property为construct-arg

<!-- 使用构造器 -->
<bean id="person" class="pojo.Person">
    <constructor-arg index="0" type="java.lang.String" value="dzzhyk" />
    <constructor-arg index="1" type="java.lang.Integer" value="20"/>
    <constructor-arg index="2" type="java.lang.Boolean" value="true"/>
</bean>

version2.TestVersion2内容不变:

public class TestVersion2 {
    @Test
    public void test(){
        ApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = ca.getBean("person", Person.class);
        System.out.println(person);
    }
}

依然正常输出结果:


Person(name=dzzhyk, age=20, sex=true)

1.3 属性注入

使用注解方式的属性注入Bean是比较优雅的做法

首先我们需要在applicationContext.xml中开启注解支持和自动包扫描:

<context:annotation-config />
<context:component-scan base-package="pojo"/>

在pojo类中对Person类加上@Component注解,将其标记为组件,并且使用@Value注解为各属性赋初值

@Component
public class Person {
    @Value("dzzhyk")
    private String name;
    @Value("20")
    private Integer age;
    @Value("true")
    private Boolean sex;
}

然后添加新的测试类version3.TestVersion3

public class TestVersion3 {
    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = ac.getBean("person", Person.class);
        System.out.println(person);
    }
}

运行也可以得到如下结果:


Person(name=dzzhyk, age=20, sex=true)

2. Warm up again

什么?还有什么?接下来我们来聊聊Spring的两种配置方式:基于XML的配置和基于JavaConfig类的配置方式,这对于理解SpringBoot的自动装配原理是非常重要的。

首先我们在Person的基础上再创建几个pojo类:这个Person有Car、有Dog

public class Car {
    private String brand;
    private Integer price;
}
public class Dog {
    private String name;
    private Integer age;
}
public class Person {
    private String name;
    private Integer age;
    private Boolean sex;
    private Dog dog;
    private Car car;
}

2.1 基于XML的配置

接下来让我们尝试使用XML的配置方式来为一个Person注入

<bean id="person" class="pojo.Person">
    <property name="name" value="dzzhyk"/>
    <property name="age" value="20"/>
    <property name="sex" value="true"/>
    <property name="dog" ref="dog"/>
    <property name="car" ref="car"/>
</bean>
<bean id="dog" class="pojo.Dog">
    <property name="name" value="旺财"/>
    <property name="age" value="5" />
</bean>
<bean id="car" class="pojo.Car">
    <property name="brand" value="奥迪双钻"/>
    <property name="price" value="100000"/>
</bean>

然后跟普通的Bean注入一样,使用ClassPathXmlApplicationContext来加载配置文件,然后获取Bean

/**
 * 使用XML配置
 */
public class TestVersion1 {
    @Test
    public void test(){
        ClassPathXmlApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = ca.getBean("person", Person.class);
        System.out.println(person);
    }
}

输出结果如下:


Person(name=dzzhyk, age=20, sex=true, dog=Dog(name=旺财, age=5), car=Car(brand=奥迪双钻, price=100000))

2.2 基于JavaConfig类的配置

想要成为JavaConfig类,需要使用@Configuration注解

我们新建一个包命名为config,在config中新增一个PersonConfig类

@Configuration
@ComponentScan
public class PersonConfig {
    @Bean
    public Person person(Dog dog, Car car){
        return new Person("dzzhyk", 20, true, dog, car);
    }
    @Bean
    public Dog dog(){
        return new Dog("旺财", 5);
    }
    @Bean
    public Car car(){
        return new Car("奥迪双钻", 100000);
    }
}

此时我们的XML配置文件可以完全为空了,此时应该使用AnnotationConfigApplicationContext来获取注解配置

/**
 * 使用JavaConfig配置
 */
public class TestVersion2 {
    @Test
    public void test(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PersonConfig.class);
        Person person = ac.getBean("person", Person.class);
        System.out.println(person);
    }
}

仍然正常输出了结果:


Person(name=dzzhyk, age=20, sex=true, dog=Dog(name=旺财, age=5), car=Car(brand=奥迪双钻, price=100000))

3. BeanDefinition


AbstractBeanDefinition

是spring中所有bean的抽象定义对象,我把他叫做bean定义

当bean.class被JVM类加载到内存中时,会被spring扫描到一个map容器中:


BeanDefinitionMap<beanName, BeanDefinition>

这个容器存储了bean定义,但是bean此时还没有进行实例化,在进行实例化之前,还有一个


BeanFactoryPostProcessor

可以对bean对象进行一些自定义处理

我们打开BeanFactoryProcessor这个接口的源码可以发现如下内容:

/*
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
*/

在spring完成标准的初始化过程后,实现BeanFactoryPostProcessor接口的对象可以用于定制bean factory,所有的bean definition都会被加载,但是此时还没有被实例化。这个接口允许对一些bean定义做出属性上的改动。

简言之就是实现了BeanFactoryPostProcessor这个接口的类,可以在bean实例化之前完成一些对bean的改动。

大致流程我画了个图:

至此我们能总结出springIOC容器的本质:(我的理解)

由BeanDefinitionMap、BeanFactoryPostProcessor、BeanPostProcessor、BeanMap等等容器共同组成、共同完成、提供依赖注入和控制反转功能的一组集合,叫IOC容器。

4. BeanDefinition结构

既然讲到了BeanDefinition,我们来看一下BeanDefinition里面究竟定义了些什么

让我们点进AbstractBeanDefinition这个类,一探究竟:

哇!好多成员变量,整个人都要看晕了@_@

我们来重点关注以下三个成员:


private volatile Object beanClass;

private int autowireMode = AUTOWIRE_NO;

private ConstructorArgumentValues constructorArgumentValues;

4.1 beanClass

这个属性决定了该Bean定义的真正class到底是谁,接下来我们来做点实验

我们定义两个Bean类,A和B

@Component
public class A {
    @Value("我是AAA")
    private String name;
}
@Component
public class B {
    @Value("我是BBB")
    private String name;
}
  • 接下来我们实现上面的BeanFactoryPostProcessor接口,来创建一个自定义的bean后置处
/**
 * 自定义的bean后置处理器
 * 通过这个MyBeanPostProcessor来修改bean定义的属性
 * @author dzzhyk
 */
public class MyBeanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        GenericBeanDefinition defA = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");
        System.out.println("这里是MyBeanPostProcessor,我拿到了:" + defA.getBeanClassName());
    }
}

最后在XML配置文件中开启包扫描

1. <context:component-scan base-package="pojo"/>
2. <context:annotation-config />
  • 注意:这里不要使用JavaConfig类来配置bean,不然会报如下错误

ConfigurationClassBeanDefinition cannot be cast to org.springframework.beans.factory.support.GenericBeanDefinition

这个错误出自这一句:


GenericBeanDefinition defA = (GenericBeanDefinition) beanFactory.getBeanDefinition("a");

最后,我们创建一个测试类:

public class Test {
    @org.junit.Test
    public void test(){
        ClassPathXmlApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
        A aaa = ca.getBean("a", A.class);
        System.out.println("最终拿到了==> " + aaa);
    }
}

测试运行!

这里是MyBeanPostProcessor,我拿到了:pojo.A
最终拿到了==> A(name=我是AAA, b=B(name=我是BBB))

可以看到MyBeanPostProcessor成功拿到了A的Bean定义,并且输出了提示信息

接下来让我们做点坏事

我们在MyBeanPostProcessor中修改A的Bean对象,将A的beanClass修改为B.class

System.out.println("这里是MyBeanPostProcessor,我修改了:"+ defA.getBeanClassName() + " 的class为 B.class");
// 把A的class改成B
defA.setBeanClass(B.class);

重新运行Test类,输出了一些信息后:报错了!

这里是MyBeanPostProcessor,我拿到了:pojo.A
这里是MyBeanPostProcessor,我修改了:pojo.A 的class为 B.class
BeanNotOfRequiredTypeException: 
 Bean named 'a' is expected to be of type 'pojo.A' but was actually of type 'pojo.B'


我要拿到一个A类对象,你怎么给我一个B类对象呢?这明显不对

综上所述,我们可以得出beanClass属性控制bean定义的类

目录
打赏
0
5
1
0
13
分享
相关文章
|
4天前
|
SpringBoot自动配置原理
本文深入解析了SpringBoot的核心功能——自动配置,重点探讨了`org.springframework.boot.autoconfigure`及相关注解的工作机制。通过分析`@SpringBootApplication`、`@EnableAutoConfiguration`等注解,揭示了SpringBoot如何基于类路径和条件自动装配Bean
39 7
|
15天前
|
SpringBoot自动装配的原理
在SpringBoot项目的启动引导类上都有一个注解@SpringBootApplication 这个注解是一个复合注解, 其中有三个注解构成 , 分别是 ● @SpringBootConfiguration : 是@Configuration的派生注解 , 标注当前类是一个SpringBoot的配置类 ● @ComponentScan : 开启组件扫描, 默认扫描的是当前启动引导了所在包以及子包 ● @EnableAutoConfiguration : 开启自动配置(自动配置核心注解) 2.在@EnableAutoConfiguration注解的内容使用@Import注解导入了一个AutoC
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
103 12
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
94 17
springboot自动配置原理
Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use;端口冲突的原理与解决方案
本文解决了Idea启动SpringBoot程序报错:Veb server failed to start. Port 8082 was already in use的问题,并通过介绍端口的使用原理和操作系统的端口管理机制,可以更有效地解决端口冲突问题,并确保Web服务器能够顺利启动和运行。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
SpringBoot基础篇之重名Bean的解决与多实例选择
当通过接口的方式注入Bean时,如果有多个子类的bean存在时,具体哪个bean会被注入呢?系统中能否存在两个重名的bean呢?如果可以,那么怎么选择引入呢?如果不行的话又该怎么避免上面的问题呢?
1881 0
SpringBoot基础篇之重名Bean的解决与多实例选择
基于SpringBoot+Vue实现的留守儿童爱心网站设计与实现(计算机毕设项目实战+源码+文档)
博主是一位全网粉丝超过100万的CSDN特邀作者、博客专家,专注于Java、Python、PHP等技术领域。提供SpringBoot、Vue、HTML、Uniapp、PHP、Python、NodeJS、爬虫、数据可视化等技术服务,涵盖免费选题、功能设计、开题报告、论文辅导、答辩PPT等。系统采用SpringBoot后端框架和Vue前端框架,确保高效开发与良好用户体验。所有代码由博主亲自开发,并提供全程录音录屏讲解服务,保障学习效果。欢迎点赞、收藏、关注、评论,获取更多精品案例源码。
基于SpringBoot+Vue实现的家政服务管理平台设计与实现(计算机毕设项目实战+源码+文档)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!
基于SpringBoot+Vue实现的家乡特色推荐系统设计与实现(源码+文档+部署)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等