• 理解依赖注入


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

    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稍长。

  • 相关阅读:
    141. Linked List Cycle【easy】
    237. Delete Node in a Linked List【easy】
    234. Palindrome Linked List【easy】
    排序_归并排序
    排序_选择排序
    排序_快速排序
    排序_冒泡排序
    排序_希尔排序
    排序_插入排序
    121. Best Time to Buy and Sell Stock【easy】
  • 原文地址:https://www.cnblogs.com/mooba/p/11432038.html
Copyright © 2020-2023  润新知