SpringBootWeb 篇-深入了解 Bean 的管理与 SpringBoot 起步依赖、SpringBoot 自动配置原理(源码追踪:jar 包配置类如何加载到 IOC 容器中?)

SpringBootWeb 篇-深入了解 Bean 的管理与 SpringBoot 起步依赖、SpringBoot 自动配置原理(源码追踪:jar 包配置类如何加载到 IOC 容器中?)

码农世界 2024-06-06 前端 89 次浏览 0个评论

🔥博客主页: 【小扳_-CSDN博客】

❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 Bean 的管理

        1.1 Bean 的获取

        1.2 Bean 的作用域

        1.3 第三方 Bean

        1.3.1 @Bean 与 @Component 的区别

        2.0 SpringBoot 原理

        2.1 SpringBoot 起步依赖原理

        2.2 SpringBoot 自动配置原理

        2.2.1 依赖 jar 包中的配置类的 bean 对象是如何直接加载到 IOC 容器中的?

        2.2.2 使用 @ComponentScan 组件扫描

        2.2.3 使用 @Import 注解导入

        2.3 SpringBoot 自动配置原理 - 源码跟踪

        2.4 SpringBoot 自动配置原理 - 按条件装配(@Conditional 注解)


        1.0 Bean 的管理

        在 Spring 框架中,Bean 的管理是通过 Spring IOC 容器来实现的。Spring的IOC 容器负责创建、配置、管理 Bean 实例,以及管理 Bean 之间的依赖关系。Spring IOC 容器通过反射机制来实例化 Bean,并通过配置文件或注解来指定 Bean 的属性值和依赖关系。

        一般通过常见的基于注解的配置方式来完成 Bean 的管理。通过在 Bean 类上添加 @Component、@Service、@Repository 等注解,告诉 Spring 容器将该类注册为 Bean,然后在配置类中通过 @ComponentScan 来扫描并加载 Bean 。

        最后通过 @Autowired 注解来实现注入。

代码演示:

IOC 容器中的 Bean 对象:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Student {
    private String name;
    private String gender;
    private int age;
}

使用 DI 对象注入:

import junit.framework.TestCase;
import org.example.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class AppTest extends TestCase {
    @Autowired
    Student student;
    @Test
    public void test1(){
        student.setName("Tom");
        student.setAge(21);
        student.setGender("男");
        System.out.println(student);
    }
}

        1.1 Bean 的获取

        除了通过 @Autowired 注解进行对象注入以外,还可以直接通过从 IOC 容器中进行手动获取 Bean 对象。

        默认情况下,Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中,如果想要主动获取这些 bean ,可以通过如下方式:

        1)根据 name 获取 bean:Object getBean(String name)

        2)根据类型获取 bean: getBean(Class requiredType)

        3)根据 name 或者 bean(带类型转换): T getBean(Spring name,Class requiredType)

代码演示:

        1)首先获取到容器对象,通过 @Autowired 注解方式将 ApplicationContext 类型的 Bean 对象注入到变量中。

        2)再通过以上三种方式来获取指定的 bean 对象

    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void test2(){
        //根据name来获取bean对象
        Student student1 = (Student) applicationContext.getBean("student");
        System.out.println(student1);
        //根据类型来获取bean对象
        Student student2 = applicationContext.getBean(Student.class);
        System.out.println(student2);
        //根据name和类型来获取bean对象
        Student student3 = applicationContext.getBean("student",Student.class);
        System.out.println(student3);
    }

运行结果:

        说明了 Student 类型的 Bean 对象在 IOC 容器中有且仅有一份。

        1.2 Bean 的作用域

        每个 Spring 容器只会创建一个 Bean 实例,并且共享给所有依赖它的组件。这是 Spring 默认的作用域,通常适用于状态无关且线程安全的 Bean 。

常见的作用域:

        1)singleton:容器内同名称的 bean 只有一个实例(默认)。

        2)prototype:每次使用该 bean 时会创建新的实例(非单例)。

        可以通过 @Scope 注解来进行配置作用域。

代码演示:

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@AllArgsConstructor
@Component
@Scope("singleton")
public class Student {
    private String name;
    private String gender;
    private int age;
    public Student() {
        System.out.println("正在创建 IOC 容器对象");
    }
}
    @Autowired
    ApplicationContext applicationContext;
    @Test
    public void test3(){
        for (int i = 0; i < 10; i++) {
            Student student = applicationContext.getBean(Student.class);
            System.out.println(student);
        }
    }

        用 @Scope 注解来配置在 IOC 容器中只有一个 bean 对象。接着在该 Student 类中额外设置了一个无参构造器,来查看 IOC 容器中的 bean 对象什么时候会自动创建 Student 对象且将对象放入 IOC 容器中。通过 for 循环,观察从 IOC 容器中取出来的 bean 对象是否时同一个对象。

运行结果:

        默认在项目启动的时候,就会自动创建好对象,且将对象放到 IOC 容器中。

        从这里很容易就可以看出来,通过 @Scope 注解设置 "singleton" 时,从 IOC 容器中获取的 bean 对象都是同一个对象。

        接着用 @Scope 注解属性设置成 "prototype" 。

代码演示:

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@AllArgsConstructor
@Component
@Scope("prototype")
public class Student {
    private String name;
    private String gender;
    private int age;
    public Student() {
        System.out.println("正在创建 IOC 容器对象");
    }
}
    @Autowired
    ApplicationContext applicationContext;
    @Test
    public void test3(){
        for (int i = 0; i < 10; i++) {
            Student student = applicationContext.getBean(Student.class);
            System.out.println(student);
        }
    }

运行结果:

        每次获取 bean 对象时,都会先创建一个 Student 类型的对象且将放到 IOC 容器中,因此每一个获取的 bean 对象都不是同一个。

 

        1.3 第三方 Bean

        如果要管理的 bean 对象来自于第三方(不是自定义的),是无法用 @Component 及衍生注解声明 bean 的,就需要用到 @Bean 注解。

        如果要将 SAXReader 对象交给 Spring IOC 容器管理,可以通过 @Bean 注解来手动配置。若要管理的第三方 bean 对象,建议对这些 bean 进行集中分类配置,可以通过 @Configuration 注解声明一个配置类。

        1)先创建一个配置类,在创建完类之后,在该类加上一个 @Configuration 注解,这样就声明了一个配置类了。

import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
    
}

        2)创建有 SAXReader 返回值的方法,且在该方法上加上 @Bean 注解。

import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
    
    @Bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
}

        将当前方法的返回值对象交给 IOC 容器管理,成为 IOC 容器 bean 。

代码演示:

    @Autowired
    ApplicationContext applicationContext;
    @Test
    public void test3(){
        for (int i = 0; i < 10; i++) {
            Student student = applicationContext.getBean(Student.class);
            System.out.println(student);
        }
    }

运行结果:

        通过 @Bean 注解来获取到方法的返回值,再将返回值放到 IOC 容器中,这样就完成了第三方 bean 对象正常的获取了。

        1.3.1 @Bean 与 @Component 的区别

        @Bean 注解通常用于配置类中的方法上,表示将该方法返回的对象添加到 Spring 的 IOC 容器中,用来自定义 bean 对象的创建方式,灵活性更高。

        @Component 注解通常用于类级别上,表示将该类作为一个组件(bean)放入 IOC 容器中管理,Spring 会自动扫描并实例化这些组件。

        因此,@Bean 是用于方法级别的注解,通过方法返回值来手动配置 bean 对象;而 @Component 是用于类级别的注解,Spring 会自动扫描并实例化被标记的类。两者可以互补使用,让 Spring 的 IOC 容器管理更加灵活和高效。

        2.0 SpringBoot 原理

        Spring Boot 是针对 Spring 框架的一种简化搭建方式,它可以让开发人员更快速地创建基于 Spring 的应用程序。它基于 Spring 框架,提供了一套开箱即用的配置,使得开发者只需少量的配置即可快速构建和部署应用程序。

        介绍 SpringBoot 原理主要包含:起步依赖、自动配置。

        2.1 SpringBoot 起步依赖原理

        是 Spring Boot 中一个非常有用的功能,它主要用于简化和管理项目中的依赖关系。起步依赖本质上是一组 Maven 依赖的集合,而无需手动配置每个依赖项的版本和依赖关系。

        起步依赖通常以 spring-boot-starter-xxx 的形式命名,其中的 “xxx” 表示具体的功能或框架。Spring Boot 提供了一系列预定义的起步依赖,如 spring-boot-starter-web、spring-boot-starter-data-jpa、spring-boot-starter-security 等,每个起步依赖都包含了各种对应功能所需的依赖关系,开发者只需引入相应的起步依赖,然后设置少量配置即可快速启动和运行项目。

        起步依赖将一组相关的依赖打包到一个依赖中,减少了检索和管理多个依赖项的复杂性。

        简单来说,假设:A 依赖于 B ,B 依赖于 C ,通过依赖传递只导入 A 依赖之后,B、C 也就引入进来了。

        这种自动处理依赖传递的机制有助于简化依赖管理,并确保整个依赖链的所有相关依赖都能被正确地加载和使用,而不需要手动处理每个依赖。

        也就是说,因为有了依赖传递的机制,所以才有了起步依赖功能。

        2.2 SpringBoot 自动配置原理

        自动配置(Auto-Configuration)是 Spring Boot 中一个重要的特性,通过自动配置,Spring Boot 可以根据项目中的依赖和配置来自动配置应用程序的功能。

        SpringBoot 的自动配置就是当 Spring 容器启动后,一些配置类、bean 对象就自动存入到 IOC 容器中,不需要我们手动声明,从而简化了开发,省去了繁琐的配置操作。

        2.2.1 依赖 jar 包中的配置类的 bean 对象是如何直接加载到 IOC 容器中的?

        当引入一个依赖后,依赖的 jar 包中可能包含一些配置类,这些配置类中定义了一些 Bean 对象。这些 Bean 对象是如何加载到 Spring 的 IOC 容器中的呢?

        在 Spring Boot 中,这是通过组件扫描和 @Import 注解来实现的。

        2.2.2 使用 @ComponentScan 组件扫描

        当使用 @ComponentScan 进行组件扫描时,Spring 容器会扫描指定包及其子包中的所有组件,包括配置类。如果配置类中使用了 @Configuration 注解,并且在配置类中定义了 @Bean 注解的方法用于创建 bean 对象,这些 bean 对象也会被加载到 Spring 的 IOC 容器中。

举个例子:

        假设有一个jar包中的配置类如下所示:

package com.example.jarpackage.config;
@Configuration
public class JarConfig {
    @Bean
    public ExampleBean exampleBean() {
        return new ExampleBean();
    }
}

        在主应用程序的配置类中,通过 @ComponentScan 指定要扫描的包,包括 jar 包中的配置类所在的包路径:

package com.example.demo;
@Configuration
@ComponentScan(basePackages = {"com.example.jarpackage.config"})
public class AppConfig {
}

        在上面的示例中,通过 @ComponentScan 注解指定要扫描的包是 "com.example.jarpackage.config" ,这样 Spring 容器将会扫描到 jar 包中的配置类 JarConfig ,并且也会加载 JarConfig 类中通过 @Bean 注解定义的 bean 对象到 IOC 容器中。

        因此,当使用 @ComponentScan 进行组件扫描时,Spring 会将 jar 包中的配置类的 bean 对象直接加载到 IOC 容器中,使得这些 bean 对象可以被应用程序中其他组件注入和使用。

        2.2.3 使用 @Import 注解导入

        在启动类中,使用 @Import 导入的类会被 Spring 加载到 IOC 容器中,导入形式主要有以下几种:

        1)导入普通类(无论有无 @Component 注解的类)

        如果使用 @Import 导入的类是普通类,即没有任何特殊注解修饰,Spring 会将这些类直接加载到 IOC 容器中作为 bean 。这些类将在 Spring 应用程序中可用。

        2)导入配置类(@Configuration 注解的类)

        如果使用 @Import 导入的类是一个带有 @Configuration 注解的配置类,Spring 会将该配置类作为一个配置文件加载,其中定义的 @Bean 方法将被调用以创建 bean,并将这些 bean 加载到 IOC 容器中。

        3)导入 ImportSelector 接口实现类

        如果使用 @Import 导入的类是一个实现 ImportSelector 接口的类,Spring 会调用 ImportSelector 接口的实现类,根据实现类的逻辑来动态选择性地导入其他类或配置。

        实现了 ImportSelector 接口,重写 selectImports() 方法,方法的返回值是一个字符串数组。简单来说,需要导入的配置类或者普通类的类名添加到该字符串数组中,接着通过 @Import 注解的形式将该实现 ImportSelector 的类配置。最终 SpringBoot 就会自动配置加载配置类或者普通类,将其对象放到 IOC 容器中,成为 Bean 对象。

@Configuration
@Import(MyImportSelector.class)
public class AppConfig {
}

        4)@EnableXXXX注解,封装 @Import 注解

        @EnableXXXX 注解通常用来封装 @Import 注解,并提供一种更加便捷的方式来启用特定功能或配置。通过 @EnableXXXX 注解,您可以一次性导入多个相关的配置类或组件,并将它们统一地配置到 Spring 应用程序中。

        简单来说,在开发中,一些功能或配置可能需要引入多个相关的配置类或普通类,并将它们加载到 IOC 容器中作为 Bean 对象使用。为了简化配置和管理,可以由专门负责的人员将这些需要加载的类通过 @Import 注解配置在一个统一的配置类中,然后由应用程序的开发者只需要在启动类中加上 @EnableXXXX 注解即可激活这些配置。

        2.3 SpringBoot 自动配置原理 - 源码跟踪

        在 SpringBoot 项目中,跟踪依赖 jar 包中的配置类或者普通类中的 bean 对象是如何加载到 IOC 容器中的。

        从启动类中的 @SpringBootApplication 注解进行跟踪分析。在 @SpringBootApplication 注解中有三个核心注解:

        1)@SpringBootConfiguration 注解:该注解于 @Configuration 注解作用相同,用来声明当前也是一个配置类,之所以该注解于 @Configuration 注解作用相同,是因为该注解中封装了 @Configuration 注解,如图:

        2)@ComponentScan 注解:组件扫描,默认扫描当前引导类所在包及其子包。

        3)@EnableAutoConfiguration 注解:SpringBoot 实现自动化配置的核心注解。

        该注解封装了 @Import 注解且 @Import 导入的类是一个实现 ImportSelector 接口的类。

        实现了 selectImports() 方法,返回值是字符串数组类型的对象。该字符串数组封装的就是需要加载类的全类名,那么这些类就会自动的导入到 IOC 容器中。

        接着往下跟踪:

        这里出现了两个文件名:

        1)META-INF/spring.factories

        2)META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

        以上两个文件中存放的都是需要加载的配置类。

        在 SpringBoot 项目启动的时候,就会自动加载这两个文件,将该配置类的全类名都加载到字符串数组中,接着由 @Import 注解完成将 bean 对象进行注入到 IOC 容器中。最后,就可以从 IOC 容器中来获取这些 bean 对象了。

从 Maven 项目中找到 spring.factories 文件,查看文件中的内容:

打开之后:存放的都是类的全类名

从 Maven 项目中找到 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,查看文件中的内容:

同样,存放的都是类的全类名:

        这些类都会读取出来,并且通过 @Import 注解会加载到 IOC 容器中,交给 IOC 容器进行管理。

        通过类名找该类的源码进行查看,举个例子:

        当前类就是一个配置类,方法返回的是一个对象且加上了 @Bean 注解进行声明 bean 对象。当 SpringBoot 项目启动时,就会自动扫描加载该文件中的配置类,通过 @Import 注解将方法中的返回值对象都添加到 IOC 容器中。最终,就可以在我们不用手动配置下,就可以直接使用这些 bean 对象。

        2.4 SpringBoot 自动配置原理 - 按条件装配(@Conditional 注解)

        按照一定的条件进行判断,在满足给定的条件后才会注册对应的 bean 对象到 Spring IOC 容器中。

常见的条件注解:

        1)@ConditionalOnClass 注解:判断环境中是否有对应的字节码文件,才注册 bean 到 IOC 容器。

@Configuration
@ConditionalOnClass(RedisOperations.class)
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate() {
        // 创建 RedisTemplate 对象
    }
}

        例如:假设我们的应用需要使用 Redis 缓存,我们可以使用 @ConditionalOnClass 注解来判断当前应用的 classpath 下是否有 Redis 相关的类,如果存在,就会注册一个 RedisTemplate 的 Bean 到 IOC 容器中。

        2)@ConditionalOnMissingBean 注解:判断环境中没有对应的 bean ,才注册 bean 到 IOC 容器中。

@Configuration
public class CacheConfig {
    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager cacheManager() {
        // 创建自定义的 CacheManager 对象
    }
}

        假设我们需要在应用中使用自定义的 CacheManager,但是如果应用中已经有了 CacheManager,就不需要再创建一个新的了。我们可以使用 @ConditionalOnMissingBean 注解来判断当前应用上下文中是否已经有了 CacheManager,如果没有,就会创建一个自定义的 CacheManager Bean 。

        3)@ConditionalOnProperty 注解:判断配置文件中有对应属性和值,才注册 bean 到 IOC 容器中。

@Configuration
@EnableJpaRepositories
@ConditionalOnProperty(name = "spring.datasource.url")
public class JpaConfig {
    // 配置 JPA 数据源、事务管理等相关信息
}

        假设我们需要在应用中使用 JPA,但是如果没有配置相关的数据库连接信息,就不需要创建 JPA 相关的 Bean 。我们可以使用 @ConditionalOnProperty 注解来判断配置文件中是否存在相关的属性值,如果存在,就会创建 JPA 相关的 Bean 。

转载请注明来自码农世界,本文标题:《SpringBootWeb 篇-深入了解 Bean 的管理与 SpringBoot 起步依赖、SpringBoot 自动配置原理(源码追踪:jar 包配置类如何加载到 IOC 容器中?)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,89人围观)参与讨论

还没有评论,来说两句吧...

Top