IOC 学习

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

上层建筑依赖下层建筑

—每一个类的构造函数都直接调用了底层代码的构造函数

Car car = new Car();
System.out.println(car.getDiPan().getLunZi().getSize());
public class Car {
    private DiPan diPan;
    Car(){
        this.diPan = new DiPan();
    }
}
public class DiPan {
    private LunZi lunZi;
    DiPan(){
        this.lunZi = new LunZi();
    }
}
public class LunZi {
    private int size;
    LunZi(){
        this.size = 30;
    }
}

轮子尺寸变更

这种设计需要修改整个上层所有类的构造函数

Car car = new Car(40);
        System.out.println(car.getDiPan().getLunZi().getSize());
public class Car {
    private DiPan diPan;
    Car(int size){
        this.diPan = new DiPan(size);
    }
}
public class DiPan {
    private LunZi lunZi;
    DiPan(int size){
        this.lunZi = new LunZi(size);
    }
}
public class LunZi {
    private int size;
    LunZi(int size){
        this.size = size;
    }
}

我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。

 LunZi lunZi = new LunZi(40);
        DiPan diPan = new DiPan(lunZi);
        Car car = new Car(diPan);
        System.out.println(car.getDiPan().getLunZi().getSize());
public class Car {
    private DiPan diPan;
    Car(DiPan diPan){
        this.diPan = diPan;
    }
}
public class DiPan {
    private LunZi lunZi;
    DiPan(LunZi lunZi){
        this.lunZi = lunZi;
    }
}
public class LunZi {
    private int size;
    LunZi(int size){
        this.size = size;
    }
}

spring boot 中的 IOC

BeanFactory

IoC容器是一个管理Bean的容器。在Spring的定义在中,它要求所有的IoC容器都需要实现BeanFactory接口,它是一个顶级容器接口。
默认情况下,Spring IoC容器中的Bean都是以单例存在的。

由于BeanFactory的功能不够强大,因此Spring在BeanFactory的基础上,设计了一个更为高级的接口ApplicationContext,它是BeanFactory的子接口之一,在Spring的体系中,BeanFactory和ApplicationContext是最为重要的接口设计,在现实中我们使用的大部分Spring IoC容器是ApplicationContext接口的实现类。

容器管理对象,不用每次都自己取new对象。使用@Service和@Autowired提供和使用服务。

@Service
public class ArBillServiceImpl implements IArBillService {
}

注入方式

构造注入

被注入对象的构造方法的参数列表声明依赖对象的参数列表
Spring4.3+之后,constructor注入支持非显示注入方式。

 @Autowired
    public ArTplBuildController(IArTplBuildService arTplBuildService) {
        this.arTplBuildService = arTplBuildService;
    }

字段注入

    @Autowired
    private IArSysSetupService iArSysSetupService;

setter注入

被注入对象的setter()方法的参数列表声明依赖对象

接口注入

比较繁琐。已经极少使用了。
被注入对象实现的接口方法的参数列表声明依赖对象

方式 优点 缺点 场景
字段注入 简单,便于添加新的dependency 可能会出现注入失败而出现NullPointedException;在Test和其他Module不可用;不可用于final字段,从而无法保证字段的不变性。 Spring官方不推荐这种写法。
setter注入 灵活性高,便于修改依赖对象 对于仅使用setter注入的依赖对象需要进行非空检查;对象无法在构造完成后马上进入就绪状态 重新注入非默认值(构造器注入)的依赖对象;对于非必需的依赖,建议使用setter注入
构造注入 对象在构造完成之后,即已进入就绪状态,可以马上使用。 当依赖对象比较多的时候,构造方法的参数列表会比较长,维护和使用也比较麻烦,根据单一职责原则,此时应该考虑重构了。使用不慎还有可能出现循环依赖。

Spring4.x之后,注入方式应该按需选择setter或constructor注入方式。

官方为什么不推荐字段注入

  • 单一职责侵入
    添加依赖是很简单的,可能过于简单了。添加六个、十个甚至一堆依赖一点也没有困难,使得我们不易察觉违反单一职责原则的程序。
    而使用构造器注入方法时,发现违反单一职责原则的程序则相对容易了。在使用构造器方式注入时,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任。明显违背了单一职责原则(SRP:Single responsibility principle)。
  • 无法声明不变的字段。
    字段注人无法注入final字段,只有构造器注入才能注入final字段
  • 隐藏了依赖关系
    使用依赖注入容器意味着类不再对依赖对象负责,获取依赖对象的职责从类中抽离出来,IoC容器会帮你装配。当类不再为依赖对象负责,它应该更明确的使用公有的接口方法或构造器,使用这种方式能很清晰的了解类需要什么,也能明确它是可选的(setter注入)还是强制的(构造器注入)。
  • 依赖注入容器紧耦合
    依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖对象。换句话说,这个类应该是一个简单的POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。只有这样,你才能在单元测试中实例化这个类而不必去启动依赖注入容器,实现测试分离(启动容器更多是集成测试)。
    然而,当使用变量直接注入时,没有一种方式能直接地实例化这个类并且满足其所有的依赖。这意味着需要手动new出来依赖对象或者只能在IoC Container范围使用。

自动装配

自动装配是为了将依赖注入“自动化”的一个简化配置的操作。
当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。
如果一个对象只通过接口来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行切换。但是这样会存在一个问题,在传统的依赖注入配置中,我们必须要明确要给属性装配哪一个bean的引用,一旦bean很多,就不好维护了。基于这样的场景,spring使用注解来进行自动装配,解决这个问题。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成。与自动装配配合的还有“自动检测”,这 个动作会自动识别哪些类需要被配置成bean,进而来进行装配。这样我们就明白了,自动装配是为了将依赖注入“自动化”的一个简化配置的操作。

装配方式

  • byName就是会将与属性的名字一样的bean进行装配。
  • byType就是将同属性一样类型的bean进行装配。-- constructor就是通过构造器来将类型与参数相同的bean进行装配。
  • autodetect是constructor与byType的组合,会先进行constructor,如果不成功,再进行byType。

@Autowired注解是byType类型的,这个注解可以用在属性上面,setter方面上面以及构造器上面。使用这个注解时,就不需要在类中为属性添加setter方法了。但是这个属性是强制性的,也就是说必须得装配上,如果没有找到合适的bean能够装配上,就会抛出异常:`NoSuchBeanDefinitionException,如果required=false时,则不会抛出异常。另一种情况是同时有多个bean是一个类型的,也会抛出这个异常。此时需要进一步明确要装配哪一个Bean,这时可以组合使用@Qualifier注解,值为Bean的名字即可。@Qualifier注解使用byName进行装配,这样可以在多个类型一样的bean中,明确使用哪一个名字的bean来进行装配。@Qualifier注解起到了缩小自动装配候选bean的范围的作用。

循环依赖

org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'arBillServiceImpl': Requested bean is currently in creation: 
Is there an unresolvable circular reference?
The dependencies of some of the beans in the application context form a cycle:

   arBillController (field private com.ufgov.ar.modules.bill.service.IArBillService com.ufgov.ar.modules.bill.controller.ArBillController.arBillService)
┌─────┐
|  arBillServiceImpl defined in file [D:\IdeaProjects\ar-server-8.20\target\classes\com\ufgov\ar\modules\bill\serviceImpl\ArBillServiceImpl.class]
↑     ↓
|  arTplBuildServiceImpl (field private com.ufgov.ar.modules.bill.service.IArBillService com.ufgov.ar.modules.tplset.serviceImpl.ArTplBuildServiceImpl.arBillService)
└─────┘

解决方法

  • 重新设计
  • 改用setter注入方式(推荐)
    与constructor注入不同,setter是按需注入的,并且允许依赖对象为null;
  • @Lazy注解
    @Lazy延迟初始化
  • 使用ApplicationContextAware, InitializingBean
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容