理解依赖注入

一个能够发挥功能的应用免不了各个组件之间相互协作,并随着项目的复杂度变高而变得复杂,这些协作就是所谓的依赖。传统的做法是每个对象负责管理与自己相关的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。比如:

public class DamselRescuingKnight implements Knight {
    private RescueDamselQuest quest;
    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }
 
    public void embarkOnQuest() {
        quest.embark();
    }
}

在这个例子中:

  1. DamselRescuingKnight的构造函数中构造了一个RescueDamselQuest对象,使得两个对象紧耦合。
  2. 单元测试困难:必须保证当embarkOnQuest()调用的时候,对象quest的方法也能被成功调用

紧耦合带来的副作用比较多,比如在更改RescueDamselQuest类中的代码的时候可能会牵扯到以之作为属性的类;当我们需要RescueDamselQuest的功能的时候用这种方式固然比较快,但是要使用另一个类似功能的时候又得去管理其他对象的引用,每增加一个功能就牵扯到多个组件的代码。

依赖注入可以从一定程度上解决这种紧耦合带来的问题

POJO

通常被称为Plain Ordinary Java Object,也就是普通的Java对象。它的内在含义是指那些从未从任何类继承、也没有实现任何接口、更没有被其他框架侵入的Java对象。这样的Java对象简单灵活,能够任意扩展。Spring的依赖注入能够发挥POJO的潜能,使得它们在没有被侵入的情况下发挥组件作用,这样既能实现功能又能保持组件之间的松耦合性。通常POJO有一些private的参数作为对象的属性。然后针对每个参数定义了get和set方法作为访问的接口。例如:

public class User {
    private long id;
    private String name;
    public void setId(long id) {
        this. id = id;
    }
    public void setName(String name) {
        this. name=name;
    }
    public long getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}

依赖注入

所谓依赖注入,就是通过第三方组件管理协调对象之间的依赖关系,对象无需自行创建或管理它们之间的依赖关系。
依赖关系将被自动注入到需要它们的对象中去。下面通过一个例子说明一下:

public class BraveKnight() {
    private Quest quest;
    public BraveKnight(Quest quest) {
        this.quest = quest;
    }
    public embarkOnQuest() {
        quest.embark();
    }
}

这个例子和第一个例子对比的区别是,它并没有在构造函数中自行创建一个对象去然后去管理,而是在构造器中作为参数传入了一个对象。这就其中一种依赖注入的方式,即构造器注入(constructor injection)

更重要的是,传入的Quest类只是一个接口,这意味着所有继承了该接口的类都可以传入并在BraveKnight中发挥作用,就不用每增加一个具体功能就要在类中重新管理一个新的对象。当然还可以传递多个参数给构造函数。

而且我们可以很方便的对该类进行测试,这里用一个mock实现就能对其进行测试。

mock可以创建模拟对象的实例,它强调业务逻辑的连通性,一般用于单例测试和集成测试。

import static org.mockito.Mockito.*; //一种导入类里的静态方法的方式
import org.junit.Test;
public class BraveKnithtTest() {
    public void knightShouldEmbarkOnQuest() {
        Quest mockQuest = mock(Quest.class); //用mock静态方法创建实例
        BraveKnight  knight = new BraveKnight(mockQuest); //把mockQuest注入一个类中
        knight.embarkOnQuest();
    }
}

mock测试可以检测这个类是可用的。可是当我们有了一个继承了Quest的具体的类之后,这个类和BraveKnight之间是具体怎么协作的呢?

创建应用组件之间协作的行为通常称为装配(wiring),spring两种常用的装配方式是xml文件装配Java语言装配。比如SlayDragonQuest类继承了Quest类,那么使用xml装配方式把它注入到BraveKnight中的方式如下:

<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- definition of the BraveKnight bean -->
<bean  id="knight"  class="sia.knights.BraveKnight">
    <constructor-arg  ref="quest"  />
</bean>

<!-- definition of the SlayDragonQuest bean -->
<bean  id="quest"  class="sia.knights.SlayDragonQuest"></bean>
</beans>

在xml文件中这两个类都被声明为spring的bean,对于BraveKnight,它使用constructor-arg这个参数把quest这个bean作为自己的构造器参数,实现它对SlayDragonQuest的依赖。

在这个例子中,尽管BraveKnight依赖于Quest,但是它并不知道传递给它的是什么类型的Quest,这样我们可以随时通过改变传递给它的Quest实现不同的功能且不必改动其内部代码,这就是一种松耦合。

当需要启动应用的时候只需要使用spring的应用上下文(Application Context)装载bean的定义并把它们组装起来。对于上述使用xml文件配置的bean,可以用ClassPathXmlApplicationContext作为应用上下文,启动方式如下:

import  org.springframework.context.support.ClassPathXmlApplicationContext;

public  class  KnightMain {
    public  static  void  main(String[] args) throws  Exception {
        ClassPathXmlApplicationContext  context  =
                new  ClassPathXmlApplicationContext("META-INF/spring/knight.xml");
        Knight  knight  =  context.getBean(Knight.class); //获取knight bean
        knight.embarkOnQuest(); //使用knight
        context.close();
    }
}

控制反转(IoC)

提到了依赖注入,就不得不提一个同样很出名的术语“控制反转”。
控制反转包含了控制反转两方面的内容。即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。具体到例子中,这里的“某一接口”就是指的上面的Quest接口,它在调用类BraveKnight中使用哪个实现类的选择权不是由BraveKnight本身决定的,而是由spring容器借由Bean配置(可以是xml也可以是java config)来进行控制的。由于控制反转这个术语不够直观,所以Martin Fowler使用了依赖注入(Dependency Injection)来解释这种模式。

BeanFactory和ApplicationContext

IoC容器底层是通过Java语言的反射机制实例化bean并建立Bean之前的依赖关系。Spring的IOC容器在实现这些基础功能的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布,资源装载等高级服务。

Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory使得管理不同类型的Java对象成为可能,应用上下文(com.springframework.beans.factory.ApplicationContext)建立的BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。我们一般称BeanFactory为Ioc容器,而称ApplicationContext为应用上下文。在某些场景下,也将ApplicationContext成为Spring容器。简单来说,BeanFactory是Spring框架的基础设施,面向Spring本身; ApplicationContext面向Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory。

ApplicationContext的初始化和BeanFactory有一个重大的区别:BeanFactory在初始化的时候,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean;而前者则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间要比BeanFactory稍长。

posted @ 2019-08-29 21:06  木白的菜园  阅读(660)  评论(0编辑  收藏  举报