依赖注入 订阅
依赖注入(Dependency Injection)是Spring框架的核心之一。 展开全文
依赖注入(Dependency Injection)是Spring框架的核心之一。
信息
外文名
DI
中文名
依赖注入
企业微信发展历程
当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。依赖注入有两种:设值注入、构造注入所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。
收起全文
精华内容
下载资源
问答
  • Spring源码揭秘之依赖注入和循环依赖问题分析前言依赖注入的入口方法依赖注入流程分析AbstractBeanFactory#getBeanAbstractBeanFactory#doGetBeanAbstractAutowireCapableBeanFactory#...bean 实例...

    前言

    在面试中,经常被问到 SpringIOCDI(依赖注入),很多人会觉得其实 IOC 就是 DI,但是严格上来说这两个其实并不等价,因为 IOC 注重的是存,而依赖注入注重的是取,实际上我们除了依赖注入还有另一种取的方式那就是依赖查找,可以把依赖注入和依赖查找都理解成 IOC 的实现方式。

    依赖注入的入口方法

    上一篇我们讲到了 IOC 的初始化流程,不过回想一下,是不是感觉少了点什么?IOC 的初始化只是将 Bean 的相关定义文件进行了存储,但是好像并没有进行初始化,而且假如一个类里面引用了另一个类,还需要进行赋值操作,这些我们都没有讲到,这些都属于我们今天讲解的依赖注入。

    默认情况下依赖注入只有在调用 getBean() 的时候才会触发,因为 Spring 当中默认是懒加载,除非明确指定了配置 lazy-init=false,或者使用注解 @Lazy(value = false),才会主动触发依赖注入的过程。

    依赖注入流程分析

    在分析流程之前,我们还是看下面这个例子:

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    applicationContext.getBean("myBean");
    applicationContext.getBean(MyBean.class);
    

    我们的分析从 getBean() 方法开始。

    AbstractBeanFactory#getBean

    在前面我们讲到了一个顶层接口 BeanFactory 中定义了操作 Bean 的相关方法,而 ApplicationContext 就间接实现了 BeanFactory 接口,所以其调用 getBean() 方法会进入到 AbstractBeanFactory 类中的方法:

    在这里插入图片描述

    可以看到,这里调用之后直接就看到 doXXX 方法了,

    AbstractBeanFactory#doGetBean

    进入 doGetBean 这个方法进去之后呢,会有一系列判断,主要有以下几个方面:

    1. 当前类是不是单例,如果是的话而且单例已经被创建好,那么直接返回。
    2. 当前原型 bean 是否正在创建,如果是的话就认为产生了循环依赖,抛出异常。
    3. 手动通过 @DependsOn 注解或者 xml 配置中显式指定的依赖是否存在循环依赖问题,存在的话直接抛出异常。
    4. 当前的 BeanFactory 中的 beanDefinitionMap 容器中是否存在当前 bean 对应的 BeanDefinition,如果不存在则会去父类中继续获取,然后重新调用其父类对应的 getBean() 方法。

    经过一系列的判断之后,会判断当前 Bean 是原型还是单例,然后走不同的处理逻辑,但是不论是原型还是单例对象,最终其都会调用 AbstractAutowireCapableBeanFactory 类中的 createBean 方法进行创建 bean 实例

    在这里插入图片描述

    AbstractAutowireCapableBeanFactory#createBean

    这个方法里面会先确认当前 bean 是否可以被实例化,然后会有两个主要逻辑:

    1. 是否返回一个代理对象,是的话返回代理对象。
    2. 直接创建一个 bean 对象实例。

    这里面第一个逻辑我们不重点分析,在这里我们主要还是分析第二个逻辑,如何创建一个 bean 实例:

    在这里插入图片描述

    AbstractAutowireCapableBeanFactory#doCreateBean

    这又是一个以 do 开头的方法,说明这里面会真正创建一个 bean 实例对象,在分析这个方法之前,我们先自己来设想一下,假如是我们自己来实现,在这个方法需要做什么操作?

    在这个方法中,最核心的就是做两件事:

    1. 实例化一个 bean 对象。
    2. 遍历当前对象的属性,如果需要则注入其他 bean,如果发现需要注入的 bean 还没有实例化,则需要先进行实例化。

    创建 bean 实例(AbstractAutowireCapableBeanFactory#createBeanInstance)

    doCreateBean 方法中,会调用 createBeanInstance 方法来实例化一个 bean。这里面也会有一系列逻辑去处理,比如判断这个类是不是具有 public 权限等等,但是最终还是会通过反射去调用当前 bean 的无参构造器或者有参构造器来初始化一个 bean 实例,然后再将其封装成一个 BeanWrapper 对象返回。

    不过如果这里调用的是一个有参构造器,而这个参数也是一个 bean,那么也会触发先去初始化参数中的 bean,初始化 bean 实例除了有参构造器形式之外,相对还是比较容易理解,我们就不过多去分析细节,主要重点是分析依赖注入的处理方式。

    依赖注入(AbstractAutowireCapableBeanFactory#populateBean)

    在上面创建 Bean 实例完成的时候,我们的对象并不完整,因为还只是仅仅创建了一个实例,而实例中的注入的属性却并未进行填充,所以接下来就还需要完成依赖注入的动作,那么在依赖注入的时候,如果发现需要注入的对象尚未初始化,还需要触发注入对象的初始化动作,同时在注入的时候也会分为按名称注入和按类型注入(除此之外还有构造器注入等方式):

    在这里插入图片描述

    我们在依赖注入的时候最常用的是 @Autowired@Resource 两个注解,而这连个注解的区别之一就是一个按照类型注入,另一个优先按照名称注入(没有找到名称就会按照类型注入),但是实际上这两个注解都不会走上面的按名称注入和按类型注入的逻辑,而是都是通过对应的 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 两个 Bean 的后置处理器来实现的,而且 @Resource 注解当无法通过名称找到 Bean 时也会根据类型去注入,在这里具体的处理细节我们就不过多展开分析,毕竟我们今天的目标是分析整个依赖注入的流程,如果过多纠结于这些分支细节,反而会使大家更加困惑。

    上面通过根据名称或者根据属性解析出依赖的属性之后,会将其封装到对象 MutablePropertyValues(即:PropertyValues 接口的实现类) 中,最后会再调用 applyPropertyValues() 方法进行真正的属性注入:

    在这里插入图片描述

    循环依赖问题是怎么解决的

    依赖注入成功之后,整个 DI 流水就算结束了,但是有一个问题我们没有提到,那就是循环依赖问题,循环依赖指的是当我们有两个类 AB,其中 A 依赖 BB 又依赖了 A,或者多个类也一样,只要形成了一个环状依赖那就属于循环依赖,比如下面的配置就是一个典型的循环依赖配置:

    <bean id="classA" class="ClassA" p:beanB-ref="classB"/>
    <bean id="classB" class="ClassB" p:beanA-ref="classA"/>
    

    而我们前面讲解 Bean 的初始化时又讲到了当我们初始化 A 的时候,如果发现其依赖了 B,那么会触发 B 的初始化,可是 B 又依赖了 A,导致其无法完成初始化,这时候我们应该怎么解决这个问题呢?

    在了解 Spring 中是如何解决这个问题之前,我们自己先想一下,如果换成我们来开发,我们会如何解决这个问题呢?其实方法也很简单,大家应该都能想到,那就是当我们把 Bean 初始化之后,在没有注入属性之前,就先缓存起来,这样,就相当于缓存了一个半成品 Bean 来提前暴露出来供注入时使用。

    不过解决循环依赖也是有前提的,以下三种情形就无法解决循环依赖问题:

    • 构造器注入产生的循环依赖。通过构造器注入产生的循环依赖会在第一步初始化就失败,所以也无法提前暴露出来。
    • 非单例模式 Bean,因为只有在单例模式下才会对 Bean 进行缓存。
    • 手动设置了 allowCircularReferences=false,则表示不允许循环依赖。

    而在 Spring 当中处理循环依赖也是这个思路,只不过 Spring 中为了考虑设计问题,并非仅仅只采用了一个缓存,而是采用了三个缓存,这也就是面试中经常被问到的循环依赖相关的三级缓存问题(这里我个人意见是不太认同三级缓存这种叫法的,毕竟这三个缓存是在同一个类中的三个不同容器而已,并没有层级关系,这一点和 MyBatis 中使用到的两级缓存还是有区别的,不过既然大家都这么叫,咱一个凡人也就随波逐流了)。

    Spring 中解决循环依赖的三级缓存

    如下图所示,在 Spring 中通过以下三个容器(Map 集合)来缓存单例 Bean

    在这里插入图片描述

    • singletonObjects

    这个容器用来存储成品的单例 Bean,也就是所谓的第一级缓存。

    • earlySingletonObjects

    这个用来存储半成品的单例 Bean,也就是初始化之后还没有注入属性的 Bean,也就是所谓的第二级缓存。

    • singletonFactories

    存储的是 Bean 工厂对象,可以用来生成半成品的 Bean,这也就是所谓的三级缓存。

    为什么需要三级缓存才能解决循环依赖问题

    看了上面的三级缓存,不知道大家有没有疑问,因为第一级缓存和第二级缓存都比较好理解,一个成品一个半成品,这个都没什么好说的,那么为什么又需要第三级缓存呢,这又是出于什么考虑呢?

    回答这个问题之前,我梳理了有循环依赖和没有循环依赖两种场景的流程图来进行对比分析:

    没有循环依赖的创建 Bean A 流程:

    在这里插入图片描述

    有循环依赖的创建 Bean A 流程(A 依赖 BB 依赖 A):

    在这里插入图片描述

    对比这两个流程其实有一个比较大的区别,我在下面这个有循环依赖的注入流程标出来了,那就是在没有循环依赖的情况下一个类是会先完成属性的注入,才会调用 BeanPostProcessor 处理器来完成一些后置处理,这也比较符合常理也符合 Bean 的生命周期,而一旦有循环依赖之后,就不得不把 BeanPostProcessor 提前进行处理,这样在一定程度上就破坏了 Bean 的生命周期。

    但是到这里估计大家还是有疑问,因为这并不能说明一定要使用三级缓存的理由,那么这里就涉及到了 Spring Aop 了,当我们使用了 Spring Aop 之后,那么就不能使用原生对象而应该换成用代理对象,那么代理对象是什么时候创建的呢?

    实际上 Spring Aop 的代理对象也是通过 BeanPostProcessor 来完成的,下图就是一个使用了 Spring Aop 的实例对象所拥有的所有 BeanPostProcessor

    在这里插入图片描述

    在这里有一个 AnnotationAwareAspectJAutoProxyCreator 后置处理器,也就是 Spring Aop 是通过后置处理器来实现的。

    知道了这个问题,我们再来确认另一个问题,Spring 中为了解决循环依赖问题,在初始化 Bean 之后,还未注入属性之前就会将单例 Bean 先放入缓存,但是这时候也不能直接将原生对象放入二级缓存,因为这样的话如果使用了 Spring Aop 就会出问题,其他类可能会直接注入原生对象而非代理对象。

    那么这里我们能不能直接就创建代理对象存入二级缓存呢?答案是可以,但是直接创建代理对象就必须要调用 BeanPostProcessor 后置处理器,这样就使得调用后置处理器在属性注入之前了,违背了 Bean 声明周期。

    在提前暴露单例之前,Spring 并不知道当前 Bean 是否有循环依赖,所以为了尽可能的延缓 BeanPostProcessor 的调用,Spring 才采用了三级缓存,存入一个 Objectactory 对象,并不创建,而是当发生了循环依赖的时候,采取三级缓存获取到三级缓存来创建对象,因为发生了循环依赖的时候,不得不提前调用 BeanPostProcessor 来完成实例的初始化。

    我们看下加入三级缓存的逻辑:

    在这里插入图片描述

    加入三级缓存是将一个 lambda 表达式存进去,目的就是延缓创建,最后发生循环依赖的时候,从一二级缓存都无法获取到 Bean 的时候,会获取三级缓存,也就是调用 ObjectFactorygetObject() 方法,而这个方法实际上就是调用下面的 getEarlyBeanReference ,这里就会提前调用 BeanPostProcessor 来完成实例的创建。

    在这里插入图片描述

    总结

    本文主要分析了 Spinrg 依赖注入的主要流程,而依赖注入中产生的循环依赖问题又是其中比较复杂的处理方式,在本文分析过程中略去了详细的逻辑,只关注了主流程。本文主要是结合了网上一些资料然后自己 debug 调试过程得到的自己对 Spring 依赖注入的一个主要流程,如果有理解错误的地方,欢迎留言交流。

    展开全文
  • 什么是依赖注入 依赖注入的好处 Go的依赖注入-wire 依赖注入是什么? 第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前没学过Java和...
    • 什么是依赖注入
    • 依赖注入的好处
    • Go的依赖注入-wire

    依赖注入是什么?

    第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前没学过Java和spring,所以第一次接触这个词的时候是很懵的。

    依赖注入,英文名dependency injection,简称DI。依赖两个字很好理解,在软件设计上,从架构模块到函数方法都存在大大小小的依赖关系。

    比如说在new A 之前需要先new B ,A依赖于B,这时候我们就可以说B是A的依赖,A控制B,AB之间存在着耦合的关系,而代码设计思想是最好可以做到松耦合。如果某一天B需要改造那么A也需要跟着改造。这是一个依赖你可以觉得没问题,但如果是A->B->C->D->E->F之间存在一连串的依赖关系,那么改造起来就会十分麻烦。

    这个时候就需要一种东西来解开他们之间的强耦合,怎么解耦呢,只能借助第三方力量了,我们把A对B的控制权交给第三方,这种思想就称为控制反转(IOC Inversion Of Control),这个第三方称为IOC容器。而IOC容器要做的事情就是new一个B出来,然后把这个B的实例注入到A里面去,然后A就可以正常的使用基于B的方法了,这个过程被称为依赖项注入,而基于IOC的这种方法就叫做依赖注入。

    依赖注入的好处

    明白了依赖注入的思想,应该也就明白了其带来的最大好处——解耦。

    而解耦又能带来更多的好处:代码扩展性增强,代码的可维护性增强,更容易进行单元测试等等。

    那么依赖注入如何实现呢?

    Java中有以下几种方式:

    1. setter方法注入:实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
    2. 基于接口的注入:实现特定接口以供外部容器注入所依赖类型的对象。
    3. 基于构造函数的注入:实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
    4. 基于注解的注入:在代码里加上特定的关键字实现注入。

    注解是最常见的方式,它像注释一样不被当做代码来执行,而是专门供别人阅读。但注释的读者完全是人类,而注解的主要读者除了人类之外还有框架或预编译器。

    Go依赖注入-wire

    wire就是一种基于注解的依赖注入方式。wire是 Google 开源的一个依赖注入工具,我们只需要在一个特殊的go文件中告诉wire类型之间的依赖关系,它会自动帮我们生成代码,帮助我们创建指定类型的对象,并组装它的依赖。

    wire有两个基础概念,Provider(构造器)和Injector(注入器)。

    通过提供provider函数,让wire知道如何产生这些依赖对象。wire根据我们定义的injector函数签名,生成完整的injector函数,injector函数是最终我们需要的函数,它将按依赖顺序调用provider

    wire的要求很简单,新建一个wire.go文件(文件名可以随意),创建我们的初始化函数。比如,我们要创建并初始化一个Mission对象,我们就可以这样:

    //+build wireinject
    
    package main
    
    import "github.com/google/wire"
    
    func InitMission(name string) Mission {
      wire.Build(NewMonster, NewPlayer, NewMission)
      return Mission{}
    }
    

    可以看到第一行的注解:+build wireinject,表示这是一个注入器。+build其实是 Go 语言的一个特性。类似 C/C++ 的条件编译,在执行go build时可传入一些选项,根据这个选项决定某些文件是否编译。wire工具只会处理有wireinject的文件,所以我们的wire.go文件要加上这个。

    在函数中,我们调用wire.Build()将创建Mission所依赖的类型的构造器传进去。例如,需要调用NewMission()创建Mission类型,NewMission()接受两个参数一个Monster类型,一个Player类型。Monster类型对象需要调用NewMonster()创建,Player类型对象需要调用NewPlayer()创建。所以NewMonster()NewPlayer()我们也需要传给wire

    写完wire.go文件之后执行wire命令,就会自动生成一个wire_gen.go文件。

    // Code generated by Wire. DO NOT EDIT.
    
    //go:generate wire
    //+build !wireinject
    
    package main
    
    // Injectors from wire.go:
    
    func InitMission(name string) Mission {
      player := NewPlayer(name)
      monster := NewMonster()
      mission := NewMission(player, monster)
      return mission
    }
    

    可以看到wire自动帮我们生成了InitMission方法,此方法中依次初始化了player,monster和mission。之后在我们的main函数中就只需调用这个InitMission即可。

    func main() {
      mission := InitMission("dj")
    
      mission.Start()
    }
    

    而在没用依赖注入之前,我们的代码是这样的:

    func main() {
      monster := NewMonster()
      player := NewPlayer("dj")
      mission := NewMission(player, monster)
    
      mission.Start()
    }
    

    是不是简洁了很多。这里只有三个对象的初始化,如果是更多可能才会意识到依赖注入的好处。

    比如:

    wire.go文件:
    // +build wireinject
    // The build tag makes sure the stub is not built in the final build.
    
    package di
    
    import (
    	"github.com/google/wire"
    )
    
    //go:generate kratos t wire
    func InitApp() (*App, func(), error) {
    	panic(wire.Build(dao.Provider, service.Provider, http.New, grpc.New, NewApp))
    }
    
    实现文件:
    //dao
    var Provider = wire.NewSet(New, NewDB, NewRedis)
    //service
    var Provider = wire.NewSet(New, wire.Bind(new(pb.Server), new(*Service)))
    
    
    生成的wire_gen.go 文件:
    func InitApp() (*App, func(), error) {
    	redis, cleanup, err := dao.NewRedis()
    	if err != nil {
    		return nil, nil, err
    	}
    	db, cleanup2, err := dao.NewDB()
    	if err != nil {
    		cleanup()
    		return nil, nil, err
    	}
    	daoDao, cleanup3, err := dao.New(redis, db)
    	if err != nil {
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	serviceService, cleanup4, err := service.New(daoDao)
    	if err != nil {
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	engine, err := http.New(serviceService)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	server, err := grpc.New(serviceService)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	app, cleanup5, err := NewApp(serviceService, engine, server)
    	if err != nil {
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    		return nil, nil, err
    	}
    	return app, func() {
    		cleanup5()
    		cleanup4()
    		cleanup3()
    		cleanup2()
    		cleanup()
    	}, nil
    }
    
    

    所以,依赖注入到底是什么?

    封装解耦罢了。

    展开全文
  • 依赖注入

    2021-05-27 17:19:03
    依赖注入 声明:本文章属于学习笔记,根据狂神说的Spring编写 Spring官方文档:Spring官方文档 这里写目录标题依赖注入一丶DI依赖注入环境 一丶DI依赖注入环境 首先我们看这两个实体类: public class Address { ...

    依赖注入

    声明:本文章属于学习笔记,根据狂神说的Spring编写

    Spring官方文档:Spring官方文档

    一丶依赖注入:

    依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。

    二丶DI依赖注入环境

    首先我们看这两个实体类:

    public class Address {
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Address{" +
                    "address='" + address + '\'' +
                    '}';
        }
    }
    
    
    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbys;
        private Map<String,String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public String[] getBooks() {
            return books;
        }
    
        public void setBooks(String[] books) {
            this.books = books;
        }
    
        public List<String> getHobbys() {
            return hobbys;
        }
    
        public void setHobbys(List<String> hobbys) {
            this.hobbys = hobbys;
        }
    
        public Map<String, String> getCard() {
            return card;
        }
    
        public void setCard(Map<String, String> card) {
            this.card = card;
        }
    
        public Set<String> getGames() {
            return games;
        }
    
        public void setGames(Set<String> games) {
            this.games = games;
        }
    
        public String getWife() {
            return wife;
        }
    
        public void setWife(String wife) {
            this.wife = wife;
        }
    
        public Properties getInfo() {
            return info;
        }
    
        public void setInfo(Properties info) {
            this.info = info;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", address=" + address.toString() +
                    ", books=" + Arrays.toString(books) +
                    ", hobbys=" + hobbys +
                    ", card=" + card +
                    ", games=" + games +
                    ", wife='" + wife + '\'' +
                    ", info=" + info +
                    '}';
        }
    }
    
    

    之后配置beans.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="stu" class="com.kdy.pojo.Student">
            <property name="name" value="kdy"/>
        </bean>
    </beans>
    

    测试类测试:

    public class MyTest {
        @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student stu = (Student)context.getBean("stu");
            System.out.println(stu.getName());
        }
    }
    

    运行结果:
    在这里插入图片描述

    三丶依赖注入之set注入

    我们还是来用student类来进行set的注入。

    beans.xml文件的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="address" class="com.kdy.pojo.Address"></bean>
        <bean id="stu" class="com.kdy.pojo.Student">
            <property name="name" value="kdy"/>
    
            <property name="address" ref="address"/>
            <!--数组-->
            <property name="books">
                <array>
                    <value>西游记</value>
                    <value>红楼梦</value>
                </array>
            </property>
    
            <!--list-->
            <property name="hobbys">
                <list>
                    <value>抽烟</value>
                    <value>喝酒</value>
                    <value>烫头</value>
                    <value>打游戏</value>
                </list>
            </property>
            <property name="card">
                <map>
                    <entry key="姓名" value="kdy" />
                    <entry key="学号" value="041940223" />
                </map>
            </property>
            <property name="games">
                <set>
                    <value>lol</value>
                    <value>cf</value>
                    <value>dnf</value>
                </set>
            </property>
            <property name="wife">
                <null/>
            </property>
            <property name="info">
                <props>
                    <prop key="name" > kdy</prop>
                    <prop key="password" > kdy1</prop>
                    <prop key="lll" > kdy2</prop>
                </props>
            </property>
        </bean>
    </beans>
    

    测试类测试:

       @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            Student stu = (Student)context.getBean("stu");
            System.out.println(stu.toString());
        }
    

    运行结果:

    Student{name=‘kdy’, address=Address{address=‘null’}, books=[西游记, 红楼梦], hobbys=[抽烟, 喝酒, 烫头, 打游戏], card={姓名=kdy, 学号=041940223}, games=[lol, cf, dnf], wife=‘null’, info={password=kdy1, name=kdy, lll=kdy2}}

    四丶c命名和p命名空间注入

    首先如果我们要使用这个c命名或者是p命名空间注入,那么就必须要引入标签连接:

    xmlns:p=“http://www.springframework.org/schema/p”
    xmlns:c=“http://www.springframework.org/schema/c”

    userbeans.xml文件配置:
    在这里插入图片描述
    运行结果:
    在这里插入图片描述

    展开全文
  • 一、依赖注入 1.1、依赖注入简介 依赖:是指一个对象所依赖的另一个对象(即:如果一个类A 的功能实现需要借助于类B,那么就称类B是类A的依赖); 耦合:两个或两个以上的类一起构建出某个功能,若其中一个类...

    一、依赖注入

    1.1、依赖注入简介

        依赖:是指一个对象所依赖的另一个对象(即:如果一个类A 的功能实现需要借助于类B,那么就称类B是类A的依赖);

        耦合:两个或两个以上的类一起构建出某个功能,若其中一个类发生变化,导致其他依赖它的类也会发生变化(即:如果在类A的内部去实例化类B,那么两者之间会出现较高的耦合,一旦类B出现了问题,类A也需要进行对应的改造,如果这样的情况很多,即每个类之间都有很多的依赖,那么就会出现牵一发而动全身的情况,导致程序会极难维护,十分容易出现问题)。

        要解决这个问题,就要把类A对类B的控制权抽离出来,交给一个第三方去做;把控制权反转给第三方,就称作:控制反转(IOC Inversion Of Control),控制反转是一种思想,是能够解决耦合问题的一种可能的结果;

        依赖注入(Dependency Injection)就是其最典型的实现方法。由第三方(我们称作IOC容器)来控制依赖,把他通过构造函数、属性或者工厂模式等方法,注入到类A内,这样就极大程度的对类A和类B进行了解耦。

    依赖注入的核心思想:将具体类之间的依赖,尽量转换成抽象依赖。

    1.2、示例

    ①定义接口和类

    public interface IInterfaceA
    { 
    }
    
    public interface IInterfaceB
    { 
    }
    
    
    public class ClassA : IInterfaceA
    {
        private IInterfaceB _b;
    
        public ClassA(IInterfaceB b)
        {
            this._b = b;
        }
    }
    
    public class ClassB : IInterfaceB
    { 
    }

    ②此时需要获取IInterfaceA,则进行如下操作

    IInterfaceB b = new ClassB();
    IInterfaceA a = new ClassA(b);

    这个时候IInterfaceA的控制权,在实例化的时候就已经被限定死了【即:ClassA的实例】,没有任何扩展的余地,并且我们还要手工的初始化IInterfaceB,同样IInterfaceB的控制权也被限定死了。【这样的代码毫无设计、也极不利于扩展】。

    1.3、依赖注入示意图

        如果采用依赖注入,则代码如下:

    var a = container.GetService<IInterfaceA>();

        这个时候接口A和B的控制权是由容器来控制的,我们可以通过向容器中注入不同的接口实现来扩展系统的灵活性,由于将控制权交给了IoC容器,我们还可以通过配置的方式灵活的控制对象的生命周期,这一点也是手工创建对象无法实现的。

    控制反转的关系图如下(图片来源于体系结构原则 | Microsoft Docs):

        如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于类 B,而 B 类又取决于类 C,如图 所示。

    直接依赖项关系图

    直接依赖关系图 

        应用依赖关系反转原则后,A 可以调用 B 实现的抽象上的方法,让 A 可以在运行时调用 B,而 B又在编译时依赖于 A 控制的接口(因此,典型的编译时依赖项发生反转)。 运行时,程序执行的流程保持不变,但接口引入意味着可以轻松插入这些接口的不同实现

    反转依赖项关系图

     反转依赖项关系图 

    二、ASP.NET Core中的依赖注入

    Asp.Net Core3.1依赖注入项目工程下载https://download.csdn.net/download/xiaochenXIHUA/34870733icon-default.png?t=L9C2https://download.csdn.net/download/xiaochenXIHUA/34870733

    2.0、编写接口和实现类

     public interface IUser
        {
            //根据用户ID获取用户信息
            object GetUserOfId();
    
        }//Interface_end
        public interface ILog
        {
            /// <summary>
            /// 写入日志信息
            /// </summary>
            /// <param name="level">日志级别</param>
            /// <param name="info">日志信息</param>
            /// <returns>返回写入日志结果</returns>
            bool WriteLog(string level,string info);
    
    
        }//Class_end
        public class UserImpl : IUser
        {
            private ILog _Log;
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="log"></param>
            public UserImpl(ILog log)
            {
                this._Log = log;
            }
    
            /// <summary>
            /// 根据ID获取用户信息  
            /// </summary>
            /// <returns></returns>
            public object GetUserOfId()
            {
                string str = "这是没有使用缓存直接读取数据库的用户信息";
    
                _Log.WriteLog("Normal",str);
    
                return str;
            }
    
    
        }//Class_end
    using IService;
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace ConcreteService
    {
        public class UserCacheImpl : IUser
        {
            public object GetUserOfId()
            {
                string str = "使用Redies缓存策略获取的用户信息";
                return str;
            }
        }//Class_end
    
    }
     public class log : ILog
        {
            //写入日志信息
            public bool WriteLog(string level, string info)
            {
                bool result = false;
                string str = $"写入 {level} 的日志信息:{info}";
                if (!string.IsNullOrEmpty(str))
                {
                    Console.WriteLine(str);
                    result = true;
                }
              
                return result;
            }
        }//Class_end

    2.1、使用自带的依赖注入服务

        Asp.NET Core服务注册很简单,即接口和接口实现类一对一,或一对多;有三种Add方法,分别代表不同的生命周期:

    • AddSingleton:单个实例,生命周期最长。

    • AddTransient:随用随销毁,生命周期最短。

    • AddScoped:生命周期在客户端与服务器的单次会话中,只要会话不结束,就不会被销毁。

    ①在【Startup.cs类的ConfigureServices()方法下】添加实现类和接口的注入依赖关系

     public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
    
                ervices.AddScoped<IUser,UserImpl>();
                services.AddScoped<ILog,log>();
    
            }

    ②在控制器(Controller)的使用方法如下:

        [Route("api/[controller]/[action]")]
        [ApiController]
        public class UserInfoController : ControllerBase
        {
            private readonly IUser _User;
    
            public UserInfoController(IUser user)
            {
                _User = user;
            }
    
    
            /// <summary>
            /// 根据ID获取用户信息
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public object GetUserInfoOfId()
            {
                不使用依赖注入的实现
                //IUser user = new UserImpl(new log());
    
                //使用依赖注入实现
                return _User.GetUserOfId();
            }
    
           
        }//Class_end

    ③运行效果

    2.2、使用第三方依赖注入插件Autofac

    《1》添加【Autofac.Extensions.DependencyInjection】的Nuget包

    《2》添加单个类和接口的依赖

    ①在Program.cs的CreateHostBuilder()方法中添加:
    【.UseServiceProviderFactory(newAutofacServiceProviderFactory())】

    using Autofac.Extensions.DependencyInjection;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Hosting;
    
    namespace Learn_WebApiBase
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    //改用Autofac来实现依赖注入
                    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    });
        }
    }
    

    ②编写自定义注入模块:【继承Module类,重写Load()方法】

    using Autofac;
    using ConcreteService;
    using IService;
    using System;
    
    namespace Learn_WebApiBase.Services
    {
        public class CustomAutofacModule:Module
        {
            /// <summary>
            /// Autofac注册类
            /// </summary>
            /// <param name="builder"></param>
            protected override void Load(ContainerBuilder builder)
            {
                //单个内容引用
                builder.RegisterType<UserImpl>().As<IUser>();
                builder.RegisterType<log>().As<ILog>();
    
            }
    
        }//Class_end
    }

    ③在Startup.cs类中添加自定义的Autofac注入方法

    using Autofac;
    using Autofac.Extensions.DependencyInjection;
    using ConcreteService;
    using IService;
    using Learn_WebApiBase.Services;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using System;
    
    namespace Learn_WebApiBase
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
            //Autofac新增
            public ILifetimeScope AutofacContainer { get; private set; }
    
            //Autofac新增
            public void ConfigureContainer(ContainerBuilder builder)
            {
                //直接使用Autofac注册自定义内容
                builder.RegisterModule(new CustomAutofacModule());
            }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
    
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                //Autofac新增可选
                AutofacContainer = app.ApplicationServices.GetAutofacRoot();
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
    
    
        }
    }
    

    ④ 编写控制器(Controller)

        [Route("api/[controller]/[action]")]
        [ApiController]
        public class UserInfoController : ControllerBase
        {
            private readonly IUser _User;
    
            public UserInfoController(IUser user)
            {
                _User = user;
            }
    
    
            /// <summary>
            /// 根据ID获取用户信息
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public object GetUserInfoOfId()
            {
                不使用依赖注入的实现
                //IUser user = new UserImpl(new log());
    
                //使用依赖注入实现
                return _User.GetUserOfId();
            }
    
           
        }//Class_end

    ⑤运行效果:

     

    《3》批量注入所有的实现类

    ①在Program.cs的CreateHostBuilder()方法中添加:
    【.UseServiceProviderFactory(newAutofacServiceProviderFactory())】(与上面添加单个类和接口的依赖一样)。

    ②编写自定义注入模块:【继承Module类,重写Load()方法】

    
    using Autofac;
    using ConcreteService;
    using IService;
    using System;
    using System.IO;
    
    namespace Learn_WebApiBase.Services
    {
        public class CustomAutofacModule:Module
        {
            /// <summary>
            /// Autofac注册类
            /// </summary>
            /// <param name="builder"></param>
            protected override void Load(ContainerBuilder builder)
            {
    
                //多内容批量引用
                var assemblyRespositorys = System.Reflection.Assembly.Load("ConcreteService");
                //string basePath = AppContext.BaseDirectory;
                //var assemblyRespositorys = System.Reflection.Assembly.LoadFrom(Path.Combine(basePath, "ConcreteService.dll"));
                builder.RegisterAssemblyTypes(assemblyRespositorys).AsImplementedInterfaces();
            }
    
        }//Class_end
    }
    

     ③在Startup.cs类中添加自定义的Autofac注入方法(与上面添加单个类和接口的依赖一样)

     ④编写控制器(Controller) 

    using IService;
    using Microsoft.AspNetCore.Mvc;
    using System.Collections.Generic;
    
    namespace Learn_WebApiBase.Controllers
    {
        [Route("api/[controller]/[action]")]
        [ApiController]
        public class UserInfoController : ControllerBase
        {
            private readonly IEnumerable<IUser> _userList;
    
            public UserInfoController(IEnumerable<IUser> userList)
            {
                _userList = userList;
            }
    
    
            /// <summary>
            /// 根据ID获取用户信息
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public object GetUserInfoOfId()
            {
                //使用依赖注入实现
                //IUser user = new UserImpl(new log());
                List<object> tmpList = new List<object>();
    
                foreach (var item in _userList)
                {
                   tmpList.Add(item.GetUserOfId());
                }
                return tmpList;
            }
    
    
        }//Class_end
    }
    

    ⑤运行效果: 

     三、思维导图和参考资料

    3.1、思维导图

    3.2、参考资料

    体系结构原则 | Microsoft Docshttps://docs.microsoft.com/zh-cn/dotnet/architecture/modern-web-apps-azure/architectural-principles

    ASP.NET Core 依赖注入 | Microsoft Docshttps://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0

    依赖注入视频https://www.bilibili.com/video/BV1sE411y7ot?p=1

    Autofac在Asp.Net Core 中的配置官网icon-default.png?t=L9C2https://docs.autofac.org/en/latest/integration/aspnetcore.html#asp-net-core-3-0-and-generic-hosting 

    展开全文
  • PHP 依赖注入

    2021-04-20 06:45:38
    依赖注入是对于要求更易维护,更易测试,更加模块化的代码的解决方案。什么是依赖每个项目都有依赖(外界提供的输入), 项目越复杂,越需要更多的依赖。在现今的网络应用程序中,最常见的依赖是数据库,其风险在于,...
  • 什么是依赖注入

    2021-03-10 05:19:56
    理解依赖注入 依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。详细含义是:当某个角色(可能是一个Java实例,调用者)须要还有一个角色(还有一个Java实例,被调用者)的协助时,在 传统...
  • 依赖注入的模式和类型 依赖注入的模式 手动模式 - 配置或者编程的方式,提前安排注入规则 XML 资源配置元信息 Java 注解配置元信息,比如@Autowired、@Resource API 配置元信息 自动模式 - 实现方提供依赖自动关联...
  • 什么是依赖注入

    2021-03-04 09:16:16
    理解依赖注入 依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的...
  • 依赖查找 和 依赖注入

    千次阅读 2021-12-05 16:37:31
    依赖查找 和 依赖注入 这两个词看起来很相似,一到了学术上大家就喜欢玩这种概念性的文字游戏,很无奈,但也没有办法。 首先我们不带任何概念和基础的来看这两个词,仅仅分析词意。 他们文字上的区别就是一个是 查找...
  • 依赖注入之set注入

    2021-06-03 09:52:46
    本文主要介绍依赖注入中的set注入方式,直接通过实例演示 一共用到两个实体类: Address 作为Student类的引用类型 package com.study.pojo; public class Address { private String address; public String ...
  • vue3中的依赖注入依赖注入数据,依赖注入函数,依赖出入函数传参,provide,inject
  • 读了文章 《深入探討依賴注入》做下笔记和总结文章主要说了这样一个思想进阶过程:传统实现 ——Interface(依赖反转)——工廠模式 ——Constructor Injection(依赖注入)——Method Injection1.传统实现class ...
  • Golang 实现依赖注入

    2021-04-29 01:21:40
    Golang 实现依赖注入 什么是依赖注入 和 使用 uber.org/dig 对 Go 项目进行依赖注入 使用 google/wire 对 Go 项目进行依赖注入
  • 本篇主要介绍Spring的依赖注入。...● 理解基于构造函数的依赖注入● 理解基于设置函数的依赖注入● 基于自动装配的依赖注入● 基于注解的依赖注入在Spring框架下,当Bean实例 A运行过程中需要引用另外一个Bean实例B...
  • 在本文中,我将向你展示如何在 Spring Framework 的依赖项注入中使用 Project Lombok 以获得最佳实践。...依赖注入示例我提供了一些设置示例,供我们查看必须使用的各种依赖项注入选项。让我们以 Spring Servic...
  • 最近学习spring框架,对依赖注入有些模糊,遂上网翻阅资料,做了下列总结,原博客为CSDN 南夏的spring的依赖注入是什么意思,侵删!Spring 能有效地组织J2EE应用各层的对象。不管是控制层的Action对象,还是业务层的...
  • 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,其中最常见的方式就是依赖注入(Dependency ...
  • 动态依赖注入组件

    2021-04-12 16:53:24
    这篇文章主要介绍笔者一个动态依赖注入组件。具体介绍请前往github查看,这里不再过多重复赘述。本文主要以一个实际例子来演示其强大特性。 github 源码:动态依赖注入组件 这里贴一下组件的原理图: 需求背景 在...
  • .net控制台应用中使用Microsoft.Extensions.DependencyInjection依赖注入的要点记录,并以定时任务控制台应用为例进行解释。
  • Spring 依赖注入概念和三种注入方式(理解及应用) 什么是注入 要了解Spring的三种注入方式首先前提是得先了解一下什么是注入,相信很多人对这个概念都是模糊不清的,网上的解释是这样的: 依赖注入(Dependency ...
  • DI的全称是Dependency Injection,中文称之为依赖注入。它与控制反转(IoC)的含义相同,只不过这两个称呼是从两个角度描述的同一个概念。对于一个Spring初学者来说,这两种称呼很难理解,下面我们将通过简单的语言来...
  • 因此官方也是为了让咱们更好使用依赖注入框架,为咱们封装了一个新的框架——Hilt,今天一块儿来看看:框架依赖注入是啥?为何须要她?Hilt是啥,怎么用?Hilt支持哪些类的依赖注入依赖注入是啥?为何须要她?简单...
  • laravel容器包含控制反转和依赖注入,使用起来就是,先把对象bind好,需要时可以直接使用make来取就好。通常我们的调用如下。$config = $container->make(‘config‘);$connection = new Connection($this->...
  • 在 Laravel 的控制器的构造方法或者成员方法,都可以通过类型约束的方式使用依赖注入,如:public function store(Request $request){//TODO}这里 $request 参数就使用了类型约束,Request 是类型约束的类型,它是一...
  • 要想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题:DI——Dependency Injection 依赖注入IoC——Inversion of Control 控制反转1、参与者都有谁?答:一般有三方参与者,一个是某个对象;一个是IoC/...
  • 在Spring容器中为一个bean配置依赖注入有三种方式:·使用属性的setter方法注入这是最常用的方式;·使用构造器注入;·使用Filed注入(用于注解方式).使用属性的setter方法注入首先要配置被注入的bean,在该bean对应...
  • 在学习如何注入前,先看一个例子,以便于更好理解Bean: Spring依赖注入共分为三类四种: 1. Set注入【重点】   依赖注入的本质便是Set注入。 依赖:bean对象的创建依赖于容器 注入:bean对象中的所有属性由容器来...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 367,749
精华内容 147,099
关键字:

依赖注入

友情链接: myFirstProject.zip