springboot 自动装配和bean注入原理及实现

装配:创建bean,并加入IOC容器。

注入:创建bean之间的依赖关系。

1、类自动装配

SpringBoot 自动装配使得开发人员可以轻松地搭建、配置和运行应用程序,而无需手动管理大部分的 bean 和配置。

Spring Boot 的自动装配机制与模块化和复用化密切相关,它们之间存在着相互促进和互补的关系。

模块化:

Spring Boot 通过 Spring Starter 的方式提供了一种模块化的解决方案。每个 Starter 都是一个独立的模块,它包含了特定功能的配置、依赖和自动装配。

开发者可以根据项目的需求选择性地引入需要的 Starter,从而实现功能上的模块化。这使得项目结构更清晰、功能更独立,并且可以根据需求进行定制和组合。

同时,Spring Boot 本身也是一个模块化的框架,它将各种功能划分为不同的模块,如 Web、数据访问、安全等,使得开发者能够根据需要选择性地引入和使用这些功能模块。

复用化:

Spring Boot 的自动装配机制大大提高了代码的复用性。通过自动扫描和自动装配,Spring Boot 能够将各种功能模块自动集成到应用程序中,避免了重复编写和配置的工作。

Starter 的设计也是为了提高代码的复用性。开发者可以将常用的功能打包成 Starter,供其他项目引入和使用,从而避免了重复开发相似的功能。

此外,Spring Boot 的约定大于配置的原则也促进了代码的复用。通过统一的项目结构、配置规范和命名规范,开发者能够更容易地理解和使用他人编写的代码,提高了代码的可维护性和可复用性。

总的来说,Spring Boot 的自动装配机制使得模块化和复用化更加便捷和高效。开发者可以根据项目的需求选择性地引入功能模块,同时也可以将常用的功能封装成 Starter,以供其他项目复用。

这种模块化和复用化的设计理念使得 Spring Boot 在开发中得到了广泛的应用和认可。

1.1 自动装配原理(那些bean会被自动装配)

SpringBoot 会根据定义在 classpath 下的类,自动给你生成一些 Bean,并且加载到 Spring 的 Context 中。

那么,它的原理是什么呢?哪些 Bean 可以自动装配到容器里面呢?

其实在 SpringBoot 内部,会读取 classpath 下 META-INF/spring.factories 文件中的所配置的类的全类名。

我们可以找到 key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值。

可以发现就是一系列的 xxxAutoConfiguration,随便找两个类看一下,就是标记了 @Configuration 的配置类,并且通过一些条件注解,来判断、决定哪些 Bean 应该自动注入到容器中。

⚠️注意:

在SpringBoot 2.7 版本后,spring.factories 方式已经被标记为废弃,

SpringBoot 2.7 版本后建议使用新的配置文件:

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

但是,在加载到时候,会同时加载这两个文件。

比如自己可以配置需要加入的bean并在类上使用@AutoConfiguration注解。包名和配置示例如下

可以通过条件注解的方式排除某些bean加载到容器中,节省启动时间和内存,也可以通过/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件额外加载一些bean进去

1.2 自动装配的实现

1.2.1 自动装配实现

SpringBoot 是如何实现自动装配的,先看一个大概步骤:

  • 1、 组件扫描(Component Scanning):Spring Boot 使用组件扫描来查找和注册标有特定注解的 bean。默认情况下,Spring Boot 将扫描主应用程序类所在的包及其子包下的所有类。标有 @Component、@Service、@Repository 和 @Controller 注解的类将被注册为 bean。

    @SpringBootApplication(scanBasePackages = {"{procurator.info.base-package}.server", "{procurator.info.base-package}.module"})
    public class ProcuratorateServerApplication {}

我的值是sn.crc也就是说这样的包路径的会被扫描到注入IOC容器

但是下面的包就不会扫描进去

若想扫描进去就需要加扫描的路径"sn.crc.framework.desensitize.core.slider.handler"

  • 2、条件化的 bean 注册(Conditional Bean Registration):Spring Boot 使用条件化的 bean 注册来根据条件自动注册特定的 bean。这些条件可以基于环境属性、系统属性、类路径是否包含特定类等。当条件满足时,相应的 bean 将被注册到 Spring 的应用程序上下文中。

  • 3、自动配置(Auto-Configuration):Spring Boot 基于类路径中的 jar 包和已配置的条件自动配置应用程序上下文。Spring Boot 提供了大量的自动配置类,这些类负责根据条件注册特定的 bean,以及设置应用程序的默认配置。这样,开发人员就无需手动配置大部分常见的 bean 和配置,而是可以依赖于 Spring Boot 的自动配置。图示如下

  • SpringBoot 会去扫描指定的配置类,然后通过条件判断是否加载 Bean。

1.2.2 向容器注入bean

主要方式见上面的自动装配,下来举例几个

2.2.1 @Component + @ComponentScan

SpringBoot启动类的@SpringBootApplication注解中就标注了 @ComponentScan注解,这个注解默认扫描当前包及其子包下的标注有@Component、@Controller、@Service、@Repository等的类加载到容器中。

2.2.2 @Import注解
2.2.3 @Configuration + @Bean

只需要在配置类上标注上@Configuration声明配置类,在某个方法上标注@Bean并返回一个需要注入的对象即可

@Configuration
public class MyBeanConfiguration {
 
    @Bean(name = "myBean")
    public MyBean initMyBean() {
        return new MyBean();
    }
}
 
-------------------------------------------------
 
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.demo.*", "com.alibaba"}) // 需指明路径。
public class emptyDemoApplication {
 
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(emptyDemoApplication.class, args);
 
        Object myBean = context.getBean("myBean");
        System.out.println(myBean); // MyBean(filedA=null, fieldB=null)
    }
1.2.3 手动注入实现接口BeanDefinitionRegistryPostProcessor

们知道SpringBean的生命周期中有很多前后置方法,整体上可以概括为普通类对象转化为beanDefinition再转化为spring中的bean这么三个阶段。

而Spring会在启动的AbstratApplicationContxt类中的refresh方法中执行

invokeBeanFactoryPostProcessors,这个方法中会回调所有实现

BeanDefinitionRegistryPostProcessor接口的钩子方法。

可以简单理解成beanDefinition加载完毕之后,会对beanDefinition进行后置处理。所以理论上实现BeanDefinitionRegistryPostProcessor接口就可以手动将bean注入到容器中。

下面举例以手动注入HashMap为例:

public class TestBeanProcessor {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
      UptownBeanProcessor beanDefinitionRegistryPostProcessor = new UptownBeanProcessor();
      applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor);
      applicationContext.refresh();
      Object bean = applicationContext.getBean("test_map");
      System.out.println(bean);
   }
}
class UptownBeanProcessor implements BeanDefinitionRegistryPostProcessor {
   @Override
   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
      AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(HashMap.class).getBeanDefinition();
      registry.registerBeanDefinition("test_map", beanDefinition);
   }
   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
   }
}

1.3 自动装配的核心注解

Spring Boot 实现自动装配的几个核心注解包括:

  • @SpringBootApplication:这是一个组合注解,相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 - @ComponentScan 注解。它标记了主应用程序类,并告诉 Spring Boot 开始组件扫描、自动配置和装配。
  • @EnableAutoConfiguration:该注解用于启用 Spring Boot 的自动配置功能。它会根据应用程序的依赖关系和当前环境,自动注册所需的 bean。
  • @ComponentScan:该注解用于启用组件扫描,以便 Spring Boot 可以自动发现和注册标有 @Component、@Service、@Repository 和 @Controller 注解的类。
  • @ConditionalOnClass 和 @ConditionalOnMissingClass:这两个条件化注解用于根据类路径上是否存在特定的类来决定是否注册 bean。@ConditionalOnClass 在类路径上存在指定类时生效,而 @ConditionalOnMissingClass 在类路径上不存在指定类时生效。
  • @ConditionalOnBean 和 @ConditionalOnMissingBean:这两个条件化注解用于根据是否存在特定的 bean 来决定是否注册 bean。@ConditionalOnBean 在容器中存在指定的 bean 时生效,而 @ConditionalOnMissingBean 在容器中不存在指定的 bean 时生效。
  • @ConditionalOnProperty:该条件化注解用于根据配置属性的值来决定是否注册 bean。它可以根据配置文件中的属性值来决定是否启用或禁用特定的 bean。
    这些注解结合在一起,为 Spring Boot 提供了自动装配的能力。

从源码层面来说,Spring Boot 自动装配的核心实现,主要就是依赖 @SpringbootApplication 这个注解,它又是个组合注解,由3部分组成:

  • @SpringBootConfiguration,标注是一个配置类
  • @EnableAutoConfiguration,开启自动装配
    @AutoConfigurationPackage,指定默认的包规则,将主程序类所在包及其子包下的组件扫描到容器里
    @Import(AutoConfigurationImportSelector.class),导入AutoConfigurationImportSelector,并通过 selectImports 方法读取 META-INF/spring.factories 文件中配置的全类名,并按照条件过滤,注入需要的 bean
  • @ComponentScan,配置扫描路径,用来加载 bean
1.3.1 条件注解

SpringBoot条件注解

Spring 为我们提供了条件化注解,可以让我们控制 bean 在某种条件下才加载,主要就是 @Conditional 注解,通过指定条件,然后根据条件结果执行。

Spring 还为我们提供了一些已有的条件可以让我们直接使用:

@ConditionalOnClass:当类路径中存在指定的类时生效。

@ConditionalOnMissingClass:当类路径中不存在指定的类时生效。

@ConditionalOnBean:当容器中存在指定的 Bean 时生效。

@ConditionalOnMissingBean:当容器中不存在指定的 Bean 时生效。

@ConditionalOnProperty:当指定的配置属性存在且值符合条件时生效。

@ConditionalOnResource:当类路径下存在指定资源文件时生效。

@ConditionalOnWebApplication:当应用是 Web 应用时生效。

@ConditionalOnNotWebApplication:当应用不是 Web 应用时生效。

1.4 如何控制那些bean被加载

  • 1、定义bean。

定义一个类并使用@Component、@Service、@Repository 和 @Controller注解,表明该类是个bean

  • 2、sprinboot启动扫描bean,下面4样方式任一扫描的类都会被装配
    2.1、Spring Boot 将扫描主应用程序类main所在的包及其子包下的所有类。标有 @Component、@Service、@Repository 和 @Controller 注解的类将被注册为 bean。
    2.2、扫描指定包路径下的spring注解。可通过注解指定额外需要扫描的包@SpringBootApplication(scanBasePackages = {"${procurator.info.base-package}.server", "${procurator.info.base-package}.module","sn.crc.framework.desensitize.core.slider.handler"})
    比如@Component、@Service、@Lazy @Sope等spring识别的注解
    2.3、在配置文件里的bean。只需要在resource/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中写入需要注入类路径即可

    比如 该模块的其它类不需要注入只需要注入额外的几个类
    2.4、其他方式见上面的1.2.2章节的几种方式
  • 3 bena的实例化和初始化。根据扫描的 class 属性或者根据 className 来解析 Class,创建bean注册容器中
  • 4、就可以正常使用了,可以在其他bean中进行注入了

打印加载的所有的bean

ConfigurableApplicationContext context = SpringApplication.run(ProcuratorateServerApplication.class, args);
        String[] beanNames = context.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
//获取指定bean
context.getBean(类名.class);

根据IOC容器中获取bean,也可以用applicationcontext

context.getBean(类名.class);

1.4 如何自定义一个 SpringBoot starter?

Spring Boot 的自动装配机制使得模块化和复用化更加便捷和高效。

开发者可以根据项目的需求选择性地引入功能模块,同时也可以将常用的功能封装成 Starter,以供其他项目复用。

SpringBoot 这种模块化和复用化的设计理念使得 Spring Boot 在开发中得到了广泛的应用和认可。

1.4.1 社区常用starter

SpringBoot Starter用于简化开发过程并提供各种功能的集成,Spring Boot 社区提供了许多常用的 Starter,常用的如下:

  • spring-boot-starter-web:用于构建 Web 应用程序的 Starter,包括 Spring MVC、Embedded Tomcat、Jackson、Validation 等。
  • spring-boot-starter-data-jpa:用于集成 Spring Data JPA 和 Hibernate 的 Starter,用于访问和操作关系型数据库。
  • spring-boot-starter-data-mongodb:用于集成 Spring Data MongoDB 的 Starter,用于访问和操作 MongoDB 数据库。
  • spring-boot-starter-security:用于集成 Spring Security 的 Starter,提供身份验证和授权功能。
  • spring-boot-starter-test:用于测试 Spring Boot 应用程序的 Starter,包括 JUnit、Spring Test、Spring Boot Test 等。
  • spring-boot-starter-actuator:用于监控和管理 Spring Boot 应用程序的 Starter,包括健康检查、指标、日志级别设置等。
  • spring-boot-starter-log4j2:用于集成 Log4j2 日志框架的 Starter,用于记录应用程序日志。
  • spring-boot-starter-mail:用于集成邮件发送功能的 Starter,包括 JavaMail 和 Spring Framework 的邮件支持。
  • spring-boot-starter-cache:用于集成缓存支持的 Starter,包括 Spring 缓存抽象和常见的缓存实现,如 Ehcache、Redis、Caffeine 等。
  • spring-boot-starter-actuator:用于添加生产就绪功能,如指标、健康检查、审计、HTTP追踪等。
1.4.2 自定义starter

基本的结构图

自定义步骤如下,一般是一个新建的module

1、新建 SpringBoot 项目,引入 SpringBoot 相关依赖

2、创建 @Configuration 配置类,在配置类里面声明要注入的 Bean,还可以结合 @Conditional 条件注解,按需加载

@Configuration
@ConditionalOnClass(MyStarterService.class)
public class MyStarterAutoConfiguration {
  
    // 自动配置的内容,例如注册 Bean、初始化等
}

3、在项目的 resources 包下创建 META-INF/spring/ 文件,并且配置类的全限定名

4、其他项目或者模块引入即可

<dependency>
    <groupId>com.warm</groupId>
    <artifactId>custom-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

2、bean的注入实现

就是将一个对象注入到另一个对象中,以便它们可以相互协作创建依赖关系,提高了代码的可维护性和可读性

2.1 注入原理

2.2 注入的几种实现方式

2.2.1 通过属性(字段)注入

注解@Autowired或@Resource

字段注入是一种不太常用的注入方式,它使用@Autowired或者@Resource注解直接将需要注入的Bean注入到类的字段中。下面是一个示例:

 @Autowired
    private  ApplicationContext applicationContext;
  • 优点

    代码少,简洁明了。

    新增依赖十分方便,不需要修改原有代码

  • 缺点

    容易出现空指针异常。

    空指针异常不是必现的,与bean的实例化顺序有关。有时,把依赖的bean改个名字就会报空指针异常。

    会出现循环依赖的隐患。

2.2.2 通过setter方法注入

Setter方法注入是另一种常用的注入方式。开发者可以在Bean类中定义Setter方法,并使用@Autowired注解将需要注入的Bean作为参数传入。Spring容器会自动调用这些Setter方法,并将Bean注入到它们中。下面是一个示例:

    private AdminUserApi adminUserApi;

    //@Autowired是用在成员变量的Setter函数上。
    @Autowired
    public void setAdminUserApi(AdminUserApi adminUserApi) {
        this.adminUserApi = adminUserApi;
    }

但是我的通过setter方法注入为null,我通过setter方法+对象传递

if (handler instanceof AbstractSliderDesensitizationHandler) {
            ((AbstractSliderDesensitizationHandler<?>) handler).setAdminUserApi(adminUserApi);
  }
2.2.3 通过构造函数+final注入

是Springboot最为推荐的一种使用方式。

构造函数注入是最常用的注入方式之一。开发者可以在Bean类的构造函数中声明需要注入的Bean,并在应用程序启动时,Spring容器会自动将这些Bean注入到构造函数中。下面是一个简单的示例:

@Component
@SuppressWarnings("rawtypes")
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {

    private final ApplicationContext applicationContext;
    private final AdminUserApi adminUserApi;

    //@Autowired
    public StringDesensitizeSerializer(ApplicationContext applicationContext, AdminUserApi adminUserApi) {
        super(String.class);
        this.applicationContext = applicationContext;
        this.adminUserApi = adminUserApi;
    }

结果注入成功,不是null

重点

  • 不能提供无参构造方法,否则Springboot默认会加载无参的构造方法,Bean实例对象会为null
  • Springboot官方建议使用final来修饰成员变量,然后通过构造方法来进行注入。原因:final修饰的成员变量是不能够被修改的;不加final虽然也能注入Bean,但是若被其他人修改为null,可能会导致不必要的问题,所以最好是加final。

若手工写构造方法觉得麻烦,也可以使用lombok中的 @RequiredArgsConstructor

@RequiredArgsConstructor
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {

    private final ApplicationContext applicationContext;
    private final AdminUserApi adminUserApi;
 }
2.2.4 通过Qualifier注解

该 注解可以指定注入的实现类(某接口多个 实现类)

@Service
public class MyService {
    private final MyRepository repository;

    @Autowired
    public MyService(@Qualifier("myRepositoryImpl") MyRepository repository) {
        this.repository = repository;
    }
}

3、名词介绍

  • 定义:Bean 是在 Spring 容器中被实例化、管理和维护的对象。一个 Bean 可以是任何普通的 Java 对象,例如 POJO、Service、Respository、Controller 等等。将一个类声明为 Bean 的方式可以是在类级别上使用 '@Component' 注解或其派生注解('@Service'、'@Repository'、'@Controller'等),也可以是通过配置文件进行显式的声明。
  • 实例化:Spring 容器负责实例化 Bean。当应用程序启动时,Spring 容器会根据配置信息或注解扫描的结果,找到并实例化所有被标记为 Bean 的类,并将它们加入容器中。实例化的过程由 Spring 的 IoC 容器负责。

  • 管理:一旦 Bean 被实例化,Spring 容器将负责管理 Bean 的生命周期和依赖关系。它会根据配置文件或注解的信息,自动解决 Bean 之间的依赖关系,确保在需要的时候正确的注入依赖。Spring 容器还会负责销毁不再需要的 Bean。

  • 依赖注入:依赖注入是 Spring 框架的一个重要特性,它允许通过自动或显式配置的方式将 Bean 的依赖项注入到其它 Bean 中。依赖注入可以通过构造函数注入、Setter 方法注入或字段注入的方式实现,其中最常见的是使用 '@Autowired'注解进行注入。

  • 作用域:Spring 框架提供了多种作用域(scope)来管理 Bean 的生命周期。常见的作用域包括单例(Singleton)、原型(Prototype)、会话(Session)、请求(Request)等。默认情况下,Bean 是单例的,即每个容器中只存在一个实例。但可以根据需要配置其它作用域。

    @Component // 默认为单例

  • 自动装配:Spring Boot 支持自动装配(Auto - wiring),它能够根据类型或名称自动解析和注入依赖关系。通过在需要注入的字段、构造函数或 Setter 方法上使用 '@Autowired' 注解,Spring 容器会自动查找并注入对应的 Bean。

    @Component

    public class MyService {

    @Autowired

    private MyBean myBean;

    // 使用myBean的代码...

    }

总的来说,Bean 是 Spring 框架中被实例化、管理和维护的对象。通过在类上使用 '@Component' 注解或其派生注解,将一个类声明为 Bean,并将其交给 Spring 容器处理。Spring 容器负责实例化、管理和维护 Bean 的生命周期和依赖关系。通过依赖注入和自动装配,应用程序可以方便的使用和管理 Bean。

相关推荐
鹿屿二向箔1 分钟前
基于 JAVASSM(Java + Spring + Spring MVC + MyBatis)框架开发一个医院挂号系统
java·spring·mvc
工一木子9 分钟前
【Leecode】Leecode刷题之路第42天之接雨水
java·算法·leetcode
Yue1one17 分钟前
I - O (输入-输出) 字节流 字符流 缓冲流
java
w_t_y_y19 分钟前
SQL拦截(二)InnerInterceptor
java
Aliano21722 分钟前
Java的jackson库
java·开发语言
昙鱼1 小时前
Maven的了解与使用
java·maven
北纬39°的风1 小时前
从0开始搭建一个生产级SpringBoot2.0.X项目(十)SpringBoot 集成RabbitMQ
java·spring boot·redis
forestqq1 小时前
设置JAVA以适配华为2288HV2服务器的KVM控制台
java·运维·服务器
LUwantAC1 小时前
Java学习路线:Maven(一)认识Maven
java·学习·maven
勤匠1 小时前
使用 Stream 处理集合数据【Java 1.8 新特性】
java