Spring依赖注入的魔法:深入DI的实现原理【beans 五】

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

Spring依赖注入的魔法:深入DI的实现原理【beans 五】

    • 前言
    • DI的基本概念
      • 基本概念:
      • 为什么使用依赖注入:
    • 构造器注入
      • 构造器注入的基本概念:
      • 示例:
      • 多个构造函数的处理:
    • Setter方法注入
      • Setter方法注入的基本概念:
      • 示例:
      • 可选性的Setter方法:
    • 接口注入
      • 1. 定义接口:
      • 2. 实现接口:
      • 3. 使用注入后的依赖:
    • 自动装配
      • 1. 根据类型进行自动装配:
      • 2. 根据名称进行自动装配:
      • 3. 字段和方法参数的自动装配:
    • Qualifier注解
      • 示例:
    • Primary注解
      • 示例:
    • 使用java配置进行DI
      • 1. 创建一个接口和两个实现类:
      • 2. 创建一个Java配置类:
      • 3. 使用配置类进行依赖注入:

前言

在软件开发的舞台上,依赖注入是一个强大的设计模式,而Spring框架以其优雅的实现而脱颖而出。你可能已经使用了DI,但你是否真正了解它的实现原理呢?在这篇文章中,我们将打开DI的黑盒,揭开Spring DI的神秘面纱,让你更深刻地理解这一关键的框架特性。

DI的基本概念

依赖注入(Dependency Injection,简称DI)是一种软件设计模式,它用于解耦组件之间的依赖关系。在依赖注入中,组件不再负责自己依赖的对象的创建和管理,而是由外部容器(通常是一个框架或容器)负责注入依赖的对象。这种注入通常通过构造函数、方法或属性进行。

基本概念:

  1. 依赖(Dependency): 表示一个对象需要另一个对象来完成特定的功能。例如,一个类可能依赖于一个数据库连接、一个服务类或其他组件。

  2. 注入(Injection): 表示将依赖关系注入到类中。这可以通过构造函数、方法参数或属性来实现。

为什么使用依赖注入:

  1. 解耦: 依赖注入有助于降低组件之间的耦合度。组件不再直接创建或管理它们的依赖关系,而是由外部容器负责。这样,组件之间的关系更加灵活,更容易修改和维护。

  2. 可测试性: 依赖注入使得组件更容易进行单元测试。因为依赖关系被注入,测试时可以使用模拟对象或桩对象替代真实的依赖,从而更容易进行单元测试。

  3. 可维护性: 通过将依赖关系的创建和管理移到外部容器中,代码变得更加清晰、简洁,并且更容易理解和维护。每个组件只需要关注自己的功能,而不必关心如何创建和管理依赖关系。

  4. 灵活性: 依赖注入提供了更大的灵活性,可以轻松更改组件之间的关系,而无需修改组件本身的代码。这使得系统更容易适应变化和演进。

  5. 可重用性: 通过将依赖关系解耦,依赖注入可以促使更多的组件变得可重用。一个组件可以在不同的上下文中使用,而不必担心它的依赖关系。

总体而言,依赖注入是一种有助于提高代码质量、可维护性和可测试性的设计模式,特别是在大型项目和团队中。它有助于创建更灵活、可扩展且易于维护的应用程序。

构造器注入

构造器注入是一种依赖注入的方式,通过构造函数来注入一个类的依赖。在Spring中,构造器注入是一种推荐的注入方式,因为它能够确保依赖在对象创建时就被满足,从而提高对象的不可变性和一致性。以下是构造器注入的基本概念和多个构造函数的处理方法。

构造器注入的基本概念:

  1. 构造函数(Constructor): 一个类的构造函数是用于创建对象的方法。在构造函数中,可以接受依赖关系所需的参数。

  2. 构造器注入(Constructor Injection): 将依赖关系通过构造函数的参数进行注入。这意味着对象在被创建时,其依赖关系必须通过构造函数提供。

示例:

public class MyService {

    private final MyDependency myDependency;

    // 构造函数注入
    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,MyService通过构造函数接受一个MyDependency的实例,从而实现了构造器注入。

多个构造函数的处理:

如果一个类有多个构造函数,Spring会尽量通过匹配参数类型和数量的方式选择合适的构造函数。但是,在有多个构造函数的情况下,可能会出现歧义,需要通过@Autowired@Qualifier注解来明确指定使用哪个构造函数。

public class MyService {

    private final MyDependency myDependency;

    // 主要的构造函数
    @Autowired
    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 辅助的构造函数
    @Autowired
    public MyService(MyDependency myDependency, OtherDependency otherDependency) {
        this.myDependency = myDependency;
        // 处理其他依赖
    }

    // 其他业务逻辑
}

在上述例子中,通过@Autowired注解标注了两个构造函数,Spring会选择匹配的构造函数来注入依赖。如果需要明确指定使用哪个构造函数,可以使用@Qualifier注解。

public class MyService {

    private final MyDependency myDependency;

    // 主要的构造函数
    @Autowired
    public MyService(@Qualifier("primaryMyDependency") MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 辅助的构造函数
    @Autowired
    public MyService(@Qualifier("secondaryMyDependency") MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在这个例子中,@Qualifier注解用于指定使用哪个具体的MyDependency的实例。

总的来说,构造器注入是一种简单、直观、推荐的依赖注入方式,通过构造函数接受依赖关系,使得对象的创建和初始化更加清晰和可控。在有多个构造函数的情况下,通过@Autowired@Qualifier来进行注解可以更好地处理依赖关系。

Setter方法注入

Setter方法注入是一种通过Setter方法将依赖关系注入到类中的方式。在Spring中,这是另一种常见的依赖注入方式,特别适用于那些希望在对象创建后灵活设置依赖关系的情况。以下是Setter方法注入的基本概念和可选性的Setter方法的处理方法。

Setter方法注入的基本概念:

  1. Setter方法: 一个类中的Setter方法用于设置对象的属性或依赖关系。

  2. Setter方法注入: 将依赖关系通过Setter方法进行注入。这意味着对象在创建后,通过调用相应的Setter方法来提供依赖。

示例:

public class MyService {

    private MyDependency myDependency;

    // Setter方法注入
    public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,MyService通过一个名为setMyDependency的Setter方法接受一个MyDependency的实例,从而实现了Setter方法注入。

可选性的Setter方法:

有时候,某个依赖关系可能是可选的,这时你可以通过使用@Autowired注解的required属性为Setter方法设置可选性。如果required属性设置为false,则该依赖关系是可选的,如果找不到匹配的Bean,Spring容器不会报错,而是跳过这个Setter方法的注入。

public class MyService {

    private MyDependency myDependency;

    // 可选性的Setter方法注入
    @Autowired(required = false)
    public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,setMyDependency方法被标注为可选的,如果找不到匹配的MyDependency的Bean,Spring容器不会报错。

Setter方法注入的优势在于它提供了更灵活的方式来设置依赖关系,特别适用于那些依赖关系不是必需的情况。然而,在某些情况下,Setter方法注入可能导致对象处于不完全初始化的状态,因此需要谨慎使用。

接口注入

在Spring中,你可以通过在接口中定义依赖注入的方法,然后在实现这个接口的类中实现该方法,来实现接口注入。这种方式通常用于在接口中定义一些默认的行为或配置,然后在实现类中进行具体的依赖注入。以下是一个简单的示例:

1. 定义接口:

public interface DependencyInjectable {

    void injectDependency(MyDependency myDependency);
}

在上述例子中,DependencyInjectable接口定义了一个名为injectDependency的方法,用于依赖注入。

2. 实现接口:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyService implements DependencyInjectable {

    private MyDependency myDependency;

    // 实现接口的依赖注入方法
    @Override
    @Autowired
    public void injectDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,MyService类实现了DependencyInjectable接口,并在该类中使用@Autowired注解实现了injectDependency方法的依赖注入。

3. 使用注入后的依赖:

public class AnotherService {

    private final DependencyInjectable dependencyInjectable;

    public AnotherService(DependencyInjectable dependencyInjectable) {
        this.dependencyInjectable = dependencyInjectable;
    }

    public void performOperation() {
        // 使用注入后的依赖
        dependencyInjectable.injectDependency(new MyDependency());
        // 其他操作
    }
}

在上述例子中,AnotherService类通过构造函数注入了一个实现了DependencyInjectable接口的实例,并在performOperation方法中调用了injectDependency方法。

接口注入的主要优势在于它可以在接口中定义一些默认的行为或配置,然后在实现类中选择性地实现或覆盖这些方法。这使得接口可以在不同的实现类中具有不同的依赖注入逻辑,提供更大的灵活性。但需要注意的是,这种方式可能导致实现类对Spring的依赖,因此在使用时需要谨慎。

自动装配

在Spring中,@Autowired注解用于实现自动装配(Autowired),它可以自动将匹配类型的Bean注入到目标类的字段、构造函数或方法参数中。自动装配可以根据类型和名称进行匹配。以下是关于@Autowired注解的使用示例:

1. 根据类型进行自动装配:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    private final MyDependency myDependency;

    // 根据类型进行自动装配
    @Autowired
    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,MyService类通过构造函数使用@Autowired注解自动注入了一个MyDependency类型的Bean。

2. 根据名称进行自动装配:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class AnotherService {

    private final MyDependency myDependency;

    // 根据名称进行自动装配
    @Autowired
    public AnotherService(@Qualifier("specificDependency") MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,AnotherService类通过构造函数使用@Autowired注解自动注入了一个名为specificDependencyMyDependency类型的Bean。使用了@Qualifier注解来指定具体的Bean名称。

3. 字段和方法参数的自动装配:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class YetAnotherService {

    private MyDependency myDependency;

    // 字段自动装配
    @Autowired
    @Qualifier("specificDependency")
    private AnotherDependency anotherDependency;

    // 方法参数自动装配
    @Autowired
    public void setMyDependency(@Qualifier("specificDependency") MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    // 其他业务逻辑
}

在上述例子中,YetAnotherService类通过字段和方法参数分别使用@Autowired注解实现了字段和方法参数的自动注入。同样使用了@Qualifier注解来指定具体的Bean名称。

@Autowired注解是Spring中最常用的自动装配注解之一,它简化了依赖注入的过程,提高了代码的可读性和可维护性。通过@Qualifier注解,你可以更精确地控制自动装配的具体Bean。

Qualifier注解

@Qualifier注解是Spring框架提供的一种用于解决多个同类型Bean自动装配问题的机制。当有多个相同类型的Bean存在于容器中,@Qualifier注解可以与@Autowired注解一起使用,通过指定Bean的名称来明确指定要注入的具体Bean。以下是一个简单的示例:

示例:

假设有两个实现了同一个接口的Bean:

public interface MyInterface {
    // 接口定义
}
import org.springframework.stereotype.Component;

@Component("beanA")
public class BeanA implements MyInterface {
    // 实现
}
import org.springframework.stereotype.Component;

@Component("beanB")
public class BeanB implements MyInterface {
    // 实现
}

然后在另一个类中进行自动装配:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    private final MyInterface myInterface;

    // 使用@Qualifier指定具体Bean的名称
    @Autowired
    public MyService(@Qualifier("beanB") MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    // 其他业务逻辑
}

在上述例子中,MyService类通过构造函数使用@Autowired注解进行自动装配,而通过@Qualifier("beanB")注解明确指定了要注入的Bean的名称为"beanB"。这样,在存在多个实现了MyInterface接口的Bean时,Spring就能够根据@Qualifier注解找到特定的Bean来进行注入。

总体而言,@Qualifier注解是一个有效的工具,用于解决多个同类型Bean的自动装配问题。在使用时,需要确保指定的Bean名称与实际的Bean定义名称一致。

Primary注解

@Primary注解是Spring框架提供的一种机制,用于标记一个Bean作为首选的Bean。当有多个同类型的Bean存在于容器中,并且没有使用@Qualifier注解指定具体要注入的Bean时,Spring容器会优先选择标记了@Primary注解的Bean进行注入。这有助于解决多个同类型Bean的歧义性。

以下是一个简单的示例:

示例:

假设有两个实现了同一个接口的Bean:

public interface MyInterface {
    // 接口定义
}
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class PrimaryBean implements MyInterface {
    // 实现
}
import org.springframework.stereotype.Component;

@Component
public class SecondaryBean implements MyInterface {
    // 实现
}

在上述例子中,PrimaryBean类使用了@Primary注解,标记为首选的Bean。

然后在另一个类中进行自动装配:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    private final MyInterface myInterface;

    // 由于PrimaryBean标记了@Primary,它会被首选注入
    @Autowired
    public MyService(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    // 其他业务逻辑
}

在上述例子中,MyService类通过构造函数使用@Autowired注解进行自动装配,由于PrimaryBean标记了@Primary注解,它会被首选注入到myInterface字段中。

总体而言,@Primary注解是一种方便的机制,用于解决多个同类型Bean的自动装配问题,使得Spring容器能够明确知道首选的Bean是哪个。需要注意的是,使用@Primary注解时,确保只在必要的情况下使用,以避免引入歧义。****

使用java配置进行DI

在Spring中,可以使用Java配置类(通过@Configuration注解标记的类)和@Bean注解来实现依赖注入。以下是一个简单的示例,演示了如何使用Java配置进行依赖注入:

1. 创建一个接口和两个实现类:

// MyInterface.java
public interface MyInterface {
    void myMethod();
}
// MyImplementationA.java
public class MyImplementationA implements MyInterface {
    @Override
    public void myMethod() {
        System.out.println("MyImplementationA");
    }
}
// MyImplementationB.java
public class MyImplementationB implements MyInterface {
    @Override
    public void myMethod() {
        System.out.println("MyImplementationB");
    }
}

2. 创建一个Java配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyInterface myImplementationA() {
        return new MyImplementationA();
    }

    @Bean
    public MyInterface myImplementationB() {
        return new MyImplementationB();
    }
}

在上述例子中,AppConfig类使用了@Configuration注解标记为Java配置类,并通过@Bean注解定义了两个Bean,分别是myImplementationAmyImplementationB

3. 使用配置类进行依赖注入:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class MyService {

    private final MyInterface myInterface;

    @Autowired
    public MyService(MyInterface myInterface) {
        this.myInterface = myInterface;
    }

    public void performOperation() {
        myInterface.myMethod();
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService myService = context.getBean(MyService.class);
        myService.performOperation();
        context.close();
    }
}

在上述例子中,MyService类通过构造函数注入了一个MyInterface类型的Bean,而这个Bean的具体实现是通过AppConfig配置类定义的。在main方法中,通过AnnotationConfigApplicationContext加载配置类,然后获取MyService的Bean并调用performOperation方法。

这样,就实现了通过Java配置进行依赖注入。这种方式使得依赖关系更清晰,同时能够充分利用Java的编程能力进行灵活的配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/298027.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RK3568 学习笔记 : 解决 linux_sdk 编译 python 版本报错问题

前言 最近买了 【正点原子】 的 RK3568 开发板,下载了 开发板的资料,包括 Linux SDK,这个 Linux SDK 占用的空间比较大,扩展了一下 VM 虚拟机 ubuntu 20.04 的硬盘空间,编译才正常通过。 编译 RK3568 Linux SDK 时&am…

二维地图组件开发

很多的业务项目中,都会用到二维地图,二维地图功能简洁,并且很多在很多的业务中采用,维护难度也比三维地图小一些。而开发二维地图的开源库有QGis, Mapwingis等。 采用mapwingis开发了一个二维地图组件,可以…

Jmeter事务控制器聚合报告

Jmeter 事务控制器。 在Jmeter中,默认一个取样器就是一个事务 事务控制器控制其子集取样器,合并为一个事务 添加:逻辑控制器/Logic Controller -> 事务控制器/Transaction Controller TPS: 服务器每秒处理的事务数 在事务控制器下添加多…

Http与Tcp协议的原理以及应用

OSI七层模型和相关协议 七层模型从上到下如下所示: 应用层:负责应用之间的通信,处理请求和响应的具体格式表示层:对于数据格式进行处理会话层:负责建立和断开通信连接,传输层:负责建立端口之间…

gdal平面几何空间关系

关于平面几何的空间关系判定,gdal提供了8个函数,分别是:Intersects(相交),Equals(相等),Disjoint(不相交),Touches(接触&a…

LeetCode做题总结 15. 三数之和、18. 四数之和 (Java)

不会做,参考了代码随想录和力扣官方题解,对此题进行整理。 X数之和 15. 三数之和代码思路20240103重写错误1错误2Java语言点总结 18. 四数之和代码思路20240104(伪)错误1 第一次剪枝错误2 第二次剪枝错误3 溢出 15. 三数之和 代码…

C#使用纯OpenCvSharp部署yolov8-pose姿态识别

【源码地址】 github地址:https://github.com/ultralytics/ultralytics 【算法介绍】 Yolov8-Pose算法是一种基于深度神经网络的目标检测算法,用于对人体姿势进行准确检测。该算法在Yolov8的基础上引入了姿势估计模块,通过联合检测和姿势…

vue icon 本地正常 线上打包失败变乱码

出现这个原因是因为sass解析的问题 Node版本高的话可以通过升级sass版本 并且配置vue.config规避这个问题 //给sass配置的东西 这个对应的版本是sass 1.39.0 本人node版本v14 升级sass版本后出现报错css: {loaderOptions: {scss: {additionalData: import "/styles/var…

网络请求 - 异步编程详解

一、概述 网络管理模块主要提供以下功能: HTTP数据请求:通过HTTP发起一个数据请求。WebSocket连接:使用WebSocket建立服务器与客户端的双向连接。Socket连接:通过Socket进行数据传输。 HTTP和WebSocket都是啥? 比如我…

速记计算机网络传输介质分类

计算机网络中的传输介质是指用于在不同设备之间传递数据的物理媒介。传输介质的选择对于网络的性能和可靠性至关重要。在计算机网络中,常见的传输介质可以分为有线传输介质和无线传输介质两类。本文将对这两类传输介质进行详细的分类和介绍。 一、有线传输介质 有…

外包干了4个月,技术退步明显了...

先说一下自己的情况,大专生,18年通过校招进入武汉某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四…

[C]jupyter中使用C

[C]jupyter中使用C 安装使用用处 安装 https://github.com/brendan-rius/jupyter-c-kernel 下拉找到3条命令,装就可以了 mac和linux可用 python3可用, 2不可以 第二条命令可以改为 : python3 install_c_kernel 小总结:如果有问题&#xff0…

Merge还是Rebase?这次终于懂了

《Git分支管理:Merge还是Rebase?》 导语: 在Git的分支管理中,Merge和Rebase是两种常见的合并策略,每一种都有其优劣之处。究竟应该选择Merge还是Rebase,取决于项目的需求和团队的工作流程。本文将深入探讨…

echarts 仪表盘进度条 相关配置

option {series: [{type: gauge,min: 0,//最大值max: 100, //最小值startAngle: 200,//仪表盘起始角度。圆心 正右手侧为0度,正上方为90度,正左手侧为180度。endAngle: -20,//仪表盘结束角度splitNumber: 100, //仪表盘刻度的分割段数itemStyle: {color…

WPF 使用矢量字体图标

矢量字体图标 在WPF项目中经常需要显示图标,但是项目改动后,有时候需要替换和修改图标,这样非常麻烦且消耗开发和美工的时间。为了快速开发项目,节省项目时间,使用图标矢量字体图标是一个非常不错的选择。 矢量字体图标…

【java爬虫】首页显示沪深300指数走势图以及前后端整合部署方法

添加首页 本文我们将在首页添加沪深300指数成立以来的整体走势数据展示,最后的效果是这样的 单独贴一张沪深300整体走势图 我感觉从总体上来看指数还是比较稳的,没有特别大的波动,当然,这只是相对而言哈哈。 首先是前端页面 &l…

C++性能优化- perf 和火焰图的安装使用

工欲善其事必先利其器,要想做Linux下的程序性能优化,就得先知道当前性能的瓶颈在哪里。 这里主要介绍一下常用的工具:perf工具和火焰图的使用方法 本文中的命令都是自己在Ubuntu18.04系统上测试可用的,在其他系统可能会需要不同的…

数据库的连接

连接数据库 我们使用WinR输入cmd打开运行窗口 输入:sqlplus并回车 输入用户名和密码,我用的是Scott,密码我自己设置的123456,Scott默认的密码是tiger,回车 这种情况表示登录成功 在连接Scott成功的情况下创建一些数据,在我的资源里面有个Oracle数据基础可以下载,直接复制粘…

HarmonyOS 开发基础(六)Slider

HarmonyOS 开发基础(六)Slider Entry Component struct Index {build() {Row() {Column() {// Slider:ArkUI 的基础组件 滑动条组件// options 参数:Slider 基础设置Slider({// 最小值min: 20,// 最大值max: 200,// 当前值value: …

使用Spring Retry优雅的实现业务异常重试

在系统中经常遇到业务重试的逻辑,比如三方接口调用,timeout重试三遍,异常处理重试的兜底逻辑等。那你是不是还在用下面这种方式呢: 我想大家可能很多时候也会这么写,这是能想到的第一个方法,但是我们这段代…