ioc 4.0 依赖

一、Dependency

上一节介绍了容器以及容器中的 bean。容器就是一个工厂,bean 就是工厂里的各种技术工人。但是单单靠这是无法完成一个整体的应用的,你会挖坑,我会种树,他会施肥,但是你没有铁铲,我没有树苗,他没有化肥同样什么也做不了。

典型的企业应用不会只由单一的对象(或Spring的术语bean)组成。毫无疑问,即使最简单的系统也需要多个对象共同来展示给用户一个整体的应用。

依赖我们都懂,但为何要依赖注入呢,没有依赖注入照样能实现功能。那既然出现了依赖注入,肯定是原有方式某些方面不好。查看第一节依赖注入的必要性

二、Dependency Injection

Dependency Injection(依赖注入)背后的基本原理是对象之间的依赖关系(即一起工作的其它对象)只会通过以下几种方式来实现:构造器的参数、工厂方法的参数,或给由构造函数或者工厂方法创建的对象设置属性。

因此,容器的工作就是创建 bean 时注入那些依赖关系。相对于由 bean 自己来控制其实例化、直接在构造器中指定依赖关系或者类似服务定位器(Service Locator)模式 这3种自主控制依赖关系注入的方法来说,控制从根本上发生了倒转,这也正是控制反转(Inversion of Control, IoC) 名字的由来。

1. constructor

基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个依赖。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
参数明确

构造器参数解析根据参数类型进行匹配,如果bean的构造器参数类型定义非常明确,那么在bean被实例化的时候,bean定义中构造器参数的定义顺序就是这些参数的顺序,依次进行匹配

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

由于构造参数非常明确(这里我们假定 Bar和 Baz之间不存在继承关系)。因此下面的配置即使没有明确指定构造参数顺序(和类型),也会工作的很好。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>
参数不明确
package examples;

public class ExampleBean {

    private int years;

    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

他的参数都在 value = “” 下,我哪能知道谁是谁?所以补充个 type。

<bean id="exampleBean" class="examples.ExampleBean">
  <constructor-arg type="int" value="7500000"/>
  <constructor-arg type="java.lang.String" value="42"/>
</bean>

我们还可以通过index属性来显式指定构造参数的索引,比如下面的例子:

<bean id="exampleBean" class="examples.ExampleBean">
  <constructor-arg index="0" value="7500000"/>
  <constructor-arg index="1" value="42"/>
</bean>

2. Setter

通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

3. constructor or Setter

由于大量的构造器参数可能使程序变得笨拙,特别是当某些属性是可选的时候。因此通常情况下,Spring开发团队提倡使用setter注入。而且setter DI在以后的某个时候还可将实例重新配置(或重新注入)。

对于那些没有源代码的第三方类,或者没有提供setter方法的遗留代码,我们则别无选择--构造器注入将是你唯一的选择。

BeanFactory 对于它所管理的 bean 提供两种注入依赖方式(实际上它也支持同时使用构造器注入和 Setter 方式注入依赖)。需要注入的依赖将保存在 BeanDefinition 中,它能根据指定的 PropertyEditor 实现将属性从一种格式转换成另外一种格式。然而,大部份的 Spring用户 并不需要直接以编程的方式处理这些类,而是采用 XML 的方式来进行定义,在内部这些定义将被转换成相应类的实例,并最终得到一个Spring IoC容器实例。

处理bean依赖关系通常按以下步骤进行:

  • 根据定义bean的配置(文件)创建并初始化BeanFactory实例(大部份的Spring用户使用支持XML格式配置文件的BeanFactory或ApplicationContext实现)。

  • 每个bean的依赖将以属性、构造器参数、或静态工厂方法参数的形式出现。当这些bean被实际创建时,这些依赖也将会提供给该bean。

  • 每个属性或构造器参数既可以是一个实际的值,也可以是对该容器中另一个bean的引用。

  • 每个指定的属性或构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。默认情况下,Spring 会以 String 类型提供值转换成各种内置类型,比如int、long、String、boolean等。

Spring会在容器被创建时验证容器中每个bean的配置,包括验证那些bean所引用的属性是否指向一个有效的bean(即被引用的bean也在容器中被定义)。然而,在bean被实际创建之前,bean的属性并不会被设置。对于那些singleton类型和被设置为提前实例化的bean(比如ApplicationContext中的singleton bean)而言,bean实例将与容器同时被创建。而另外一些bean则会在需要的时候被创建,伴随着bean被实际创建,作为该bean的依赖bean以及依赖bean的依赖bean(依此类推)也将被创建和分配。

4. 循环 依赖

比如说,一个类A,需要通过构造器注入类B,而类B又需要通过构造器注入类A。如果为类A和B配置的bean被互相注入的话,那么Spring IoC容器将检测出循环引用,并抛出 BeanCurrentlyInCreationException异常。

对于此问题,一个可能的解决方法就是修改源代码,将某些构造器注入改为setter注入。另一个解决方法就是完全放弃构造器注入,只使用setter注入。

在两个bean之间的循环依赖将导致一个bean在被完全初始化的时候被注入到另一个bean中(如同我们常说的先有蛋还是先有鸡的情况)。

5. 举个栗子

Setter
<bean id="exampleBean" class="examples.ExampleBean">
  <property name="beanOne"><ref bean="anotherExampleBean"/></property>
  <property name="beanTwo" ref="yetAnotherBean"/>
  <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

ref 既有元素形式,又有属性形式

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }    
}
constructor
<bean id="exampleBean" class="examples.ExampleBean">
  <constructor-arg>
    <ref bean="anotherExampleBean"/>
  </constructor-arg>
  
  <constructor-arg ref="yetAnotherBean"/>
  <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

同样 ref 既有元素形式,又有属性形式。

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;
    
    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
工厂方法参数注入

如果用工厂方法创建 bean ,依赖注入的时候使用 constructor-arg 传入,传入到 工厂方法的参数中。

<bean id="exampleBean" class="examples.ExampleBean"
      factory-method="createInstance">
  <constructor-arg ref="anotherExampleBean"/>
  <constructor-arg ref="yetAnotherBean"/>
  <constructor-arg value="1"/> 
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
	// 私有
    private ExampleBean(...) {
      ...
    }
    
    public static ExampleBean createInstance (
            AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

请注意,传给static工厂方法的参数由constructor-arg元素提供,这与使用构造器注入时完全一样。
而且,重要的是,工厂方法所返回的实例的类型并不一定要与包含static工厂方法的类类型一致。(尽管在此例子中它是一致的)
非静态的实例工厂方法与此相同(除了使用factory-bean属性替代class属性外)

三、注意

如何创建一个对象:new。
从表象来看至少有四种方式,每种都脱离不了 new。

  1. 构造器
  2. 空参构造器先创建个对象,之后 set 方法。
  3. 静态工厂方法
  4. 实例工厂方法

所以上一节 bean 对象定义至少有四种方式。

依赖注入:

  1. property
  2. constructor-arg
  3. 工厂方法参数注入,使用的依然是 constructor-arg

所以这一节依赖注入是两种

要注意上节和本节的差别。一个是创建 bean 本身,一个是依赖注入。

四、依赖配置

bean的属性及构造器参数既可以引用容器中的其他bean,也可以是内联(inline)bean。在spring的XML配置中使用和元素定义。

依赖是直接量

直接量:能指定直接量的通常只有三种类型,基本类型,字符串类型和null类型

元素通过人可以理解的字符串来指定属性或构造器参数的值。JavaBean PropertyEditor将用于把字符串从java.lang.String类型转化为实际的属性或参数类型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  
  <!-- results in a setDriverClassName(String) call -->
  <property name="driverClassName">
    <value>com.mysql.jdbc.Driver</value>
  </property>
  <property name="url">
    <value>jdbc:mysql://localhost:3306/mydb</value>
  </property>
  <property name="username">
    <value>root</value>
  </property>
  <property name="password">
    <value>masterkaoli</value>
  </property>
</bean>

也可以使用’value’ 属性

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  
  <!-- results in a setDriverClassName(String) call -->
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
  <property name="username" value="root"/>
  <property name="password" value="masterkaoli"/>
</bean>

或配置一个java.util.Properties实例:

<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            
   <!-- typed as a java.util.Properties -->
   <property name="properties">
      <value>
         jdbc.driver.className=com.mysql.jdbc.Driver
         jdbc.url=jdbc:mysql://localhost:3306/mydb
      </value>
   </property>
</bean>

如果采用上面的配置,Spring容器将使用JavaBean PropertyEditor把元素中的文本转换为一个java.util.Properties实例。由于这种做法的简单,因此Spring团队在很多地方也会采用内嵌的元素来代替value属性。

依赖容器中已经定义的 bean

在或元素内部还可以使用 ref 元素。该元素用来将 bean 中指定属性的值设置为对容器中的另外一个 bean 的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。尽管都是对另外一个对象的引用,但是通过 id/name 指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。

  1. 第一种形式也是最常见的形式是通过使用< ref/> 标记指定 bean 属性的目标 bean ,通过该标签可以引用同一容器或父容器内的任何 bean(无论是否在同一XML文件中)。XML ‘bean’ 元素的值既可以是指定 bean 的 id 值或者 name 值。
<ref bean="someBean"/>
  1. 第二种形式是使用 ref 的 local 属性指定目标 bean ,它可以利用 XML 解析器来验证所引用的 bean 是否存在同一文件中。local属性值必须是目标 bean 的 id 属性值。如果在同一配置文件中没有找到引用的 bean,XML 解析器将抛出一个例外。如果目标 bean 是在同一文件内,使用 local 方式就是最好的选择(为了尽早地发现错误)。
<ref local="someBean"/>
  1. 第三种方式是通过使用 ref 的 parent 属性来引用当前容器的父容器中的 bean。parent 属性值既可以是目标 bean 的 id 值或者 name 属性值。而且目标 bean 必须在当前容器的父容器中。使用 parent 属性的主要用途是为了用某个与父容器中的 bean 同名的代理来包装父容器中的一个bean(例如,子上下文中的一个 bean 定义覆盖了他的父 bean)。
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService"  <-- notice that the name of this bean is the same as the name of the 'parent' bean
      class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="target">
          <ref parent="accountService"/>  <-- notice how we refer to the parent bean
      </property>
    <!-- insert other configuration and dependencies as required as here -->
</bean>

属性内部 bean

内部bean定义不需要有id或name属性,即使指定id 或 name属性值也将会被容器忽略。

<bean id="outer" class="...">
  <!-- instead of using a reference to a target bean, simply define the target bean inline -->
  <property name="target">
    <bean class="com.example.Person"> <!-- this is the inner bean -->
      <property name="name" value="Fiona Apple"/>
      <property name="age" value="25"/>
    </bean>
  </property>
</bean>

注意:内部bean中的scope标记及id或name属性将被忽略。内部bean总是匿名的且它们总是prototype模式的。同时将内部bean注入到包含该内部bean之外的bean是不可能的。
相当于内部类的作用

依赖是集合

<bean id="moreComplexObject" class="example.ComplexObject">

  <!-- results in a setAdminEmails(java.util.Properties) call -->
  <property name="adminEmails">
    <props>
        <prop key="administrator">administrator@example.org</prop>
        <prop key="support">support@example.org</prop>
        <prop key="development">development@example.org</prop>
    </props>
  </property>
  
  <!-- results in a setSomeList(java.util.List) call -->
  <property name="someList">
    <list>
        <value>a list element followed by a reference</value>
        <ref bean="myDataSource" />
    </list>
  </property>
  
  <!-- results in a setSomeMap(java.util.Map) call -->
  <property name="someMap">
    <map>
        <entry>
            <key>
                <value>an entry</value>
            </key>
            <value>just some string</value>
        </entry>
        <entry>
            <key>
                <value>a ref</value>
            </key>
            <ref bean="myDataSource" />
        </entry>
    </map>
  </property>
  
  <!-- results in a setSomeSet(java.util.Set) call -->
  <property name="someSet">
    <set>
        <value>just some string</value>
        <ref bean="myDataSource" />
    </set>
  </property>
</bean>
合并集合
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.com</prop>
            <prop key="support">support@example.com</prop>
        </props>
    </property>
</bean>
<bean id="child" parent="parent">
    <property name="adminEmails">
        <!-- the merge is specified on the *child* collection definition -->
        <props merge="true">
            <prop key="sales">sales@example.com</prop>
            <prop key="support">support@example.co.uk</prop>
        </props>
    </property>
</bean>
<beans>

在上面的例子中,childbean的adminEmails属性的元素上使用了merge=true属性。当childbean被容器实际解析及实例化时,其 adminEmails将与父集合的adminEmails属性进行合并。

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

注意到这里子bean的Properties集合将从父继承所有属性元素。同时子bean的support值将覆盖父集合的相应值。

强类型集合
public class Foo {
                
    private Map<String, Float> accounts;
    
    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

在foobean的accounts属性被注入之前,通过反射,利用强类型Map<String, Float>的泛型信息,Spring的底层类型转换机制将会把各种value元素值转换为Float类型,因此字符串9.99、2.75及3.99就会被转换为实际的Float类型。

“” 或 null

用于处理null值。Spring会把属性的空参数当作空字符串处理。以下的xml片断将email属性设为空字符串。

<bean class="ExampleBean">
  <property name="email"><value/></property>
</bean>

这等同于Java代码: exampleBean.setEmail("")。 而null值则可以使用元素可用来表示。例如:

<bean class="ExampleBean">
  <property name="email"><null/></property>
</bean>

同于Java代码:exampleBean.setEmail(null)。

XML配置文件简写

、及元素都支持value属性(attribute),它可以用来替代内嵌的元素。因而,以下的代码:

<property name="myProperty">
  <value>hello</value>
</property>
<constructor-arg>
  <value>hello</value>
</constructor-arg>
<entry key="myKey">
  <value>hello</value>
</entry>

简写:

<property name="myProperty" value="hello"/>
<constructor-arg value="hello"/>
<entry key="myKey" value="hello"/>

和支持类似ref的简写属性,它可用来替代整个内嵌的元素。因而,以下的代码:

<property name="myProperty">
  <ref bean="myBean">
</property>
<constructor-arg>
  <ref bean="myBean">
</constructor-arg>

等同于

<property name="myProperty" ref="myBean"/>
<constructor-arg ref="myBean"/>

map中entry元素的简写形式为key/key-ref 和 value /value-ref属性,因而,以下的代码:

<entry>
  <key>
    <ref bean="myKeyBean" />
  </key>
  <ref bean="myValueBean" />
</entry>

等同于

<entry key-ref="myKeyBean" value-ref="myValueBean"/>

五、 总结

很多吧,为什么要同一个事搞那么多写法呢,这不是折磨人么!没办法,谁让他们是搞技术的,五花八门的东西都得来一套,至于怎么抉择就是我们结合业务思考的了。
有很多东西使用率都是很低的。

为何要使用依赖注入,为了实现插件机制,在编译期都不知道哪个对象,在运行的时候动态的提供。
这样的好处就是,如果需要修改某些实现,或者新增某些实现,只需要修改配置文件即可(前提是面向接口编程)。

就像军队中,你是团长,你不需要知道你手下营长是谁,我有一张清单,在你的名字下写了一些营长的名字,那就是归你管的。如果要改变,就只改变清单即可。这样是不是很利于权利集中,战时调度更灵活。

再比如,一体机好吗,除了占用的空间少了点没有任何优点,显示屏坏了就相当于全坏了(假设的说)。
但如果你有一个方块,上面有几个接口,只要你插上显卡,网卡,键盘鼠标,主机,你就可以用了,不管之后你想换个更高级的显示器,更高级的主机,你都可以随时将插件替换就行,是不是比一体机方便的多。

3D 打印是不是有这么个理念,如果我创建一个地基接口,然后创建客厅插件,厨房插件,卫生间插件等等。我只要在地基上把这些插件安上就能用了。期间想换任何一个插件都行,只要符合接口就行。是不是建房子也简单的多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值