NiceCui - 崔世峰

一个想给航空母舰造轮子拧螺丝的程序员

疯狂造轮子 - 公众号

spring-framework-中文文档三:依赖注入DI

5.4依赖性

典型的企业应用程序不包含单个对象(或Spring的说法中的bean)。即使最简单的应用程序也有几个对象一起工作来展示最终用户将其视为一个连贯的应用程序。下一节将介绍如何从定义许多独立的bean定义到完全实现的应用程序,在这些应用程序中对象协作实现目标。

5.4.1依赖注入

依赖注入(DI)是一个过程,通过这种过程,对象可以通过构造函数参数,工厂方法参数或者在构造或返回对象实例后设置的属性来定义它们的依赖关系,也就是说,它们使用的其他对象从工厂方法。容器 在创建bean时会注入这些依赖关系。这个过程从根本上说是相反的,因此名为 控制反转(IoC),它本身通过使用类的直接构造或服务定位器模式来控制它自己的依赖关系的实例化或位置

代码与DI原理相比更加清晰,并且在对象提供依赖关系时解耦更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系位于接口或抽象基类上时,它们允许在单元测试中使用存根或模拟实现。

DI存在两种主要的变体,基于构造器的依赖注入基于Setter的依赖注入

基于构造函数的依赖注入

基于构造器的 DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖项。调用static具有特定参数工厂方法来构造bean几乎是等价的,本讨论static类似地将参数视为构造函数和工厂方法。以下示例显示了只能通过构造函数注入进行依赖注入的类。请注意,这个类没有什么 特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。

public class SimpleMovieLister {

  // the SimpleMovieLister has a dependency on a MovieFinder
  private MovieFinder movieFinder;

  // a constructor so that the Spring container can 'inject' a MovieFinder
  public SimpleMovieLister(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }

  // business logic that actually 'uses' the injected MovieFinder is omitted...
}
构造函数参数解析

使用参数的类型发生构造函数参数解析匹配。如果bean定义的构造函数参数中没有可能存在的歧义,那么在bean定义中定义构造函数参数的顺序是当实例化bean时将这些参数提供给相应构造函数的顺序。考虑以下课程:

package x.y;

public class Foo {

  public Foo(Bar bar, Baz baz) {
      // ...
  }
}

没有潜在的歧义存在,假设 BarBaz类不通过继承相关。因此,以下配置可以正常工作,并且不需要在<constructor-arg/>元素中明确指定构造函数参数索引和/或类型 

<beans>
  <bean id="foo" class="x.y.Foo">
      <constructor-arg ref="bar"/>
      <constructor-arg ref="baz"/>
  </bean>

  <bean id="bar" class="x.y.Bar"/>
  <bean id="baz" class="x.y.Baz"/>

</beans>

当引用另一个bean时,类型是已知的,并且可以发生匹配(就像前面的例子那样)。当使用简单类型时,例如 <value>true<value>Spring无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑以下课程:

package examples;

public class ExampleBean {

  // No. of years to the calculate the Ultimate Answer
  private int years;

  // The Answer to Life, the Universe, and Everything
  private String ultimateAnswer;

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

构造函数参数类型匹配

在前面的场景中,如果使用属性明确指定构造函数参数的类型,容器 可以使用简单类型的类型匹配 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 >

除了解决多个简单值的歧义之外,指定索引还解决了构造函数具有两个相同类型参数的含糊问题。请注意,该索引是基于0的

构造函数参数名称

从Spring 3.0开始,您还可以使用构造函数参数名称进行值消歧:

<bean  id = “exampleBean”  class = “examples.ExampleBean” > 
<constructor-arg  name = “years”  value = “7500000” /> 
<constructor-arg  name = “ultimateanswer”  value = “42” /> 
</ bean >

请记住,要使这项工作脱离框架,您的代码必须在启用了调试标志的情况下编译,以便Spring可以从构造函数中查找参数名称。如果你不能用调试标志编译你的代码(或者不想),你可以使用 @ConstructorProperties JDK标注明确你的构造函数参数。示例类将不得不如下所示:

package examples;

public class ExampleBean {

  // Fields omitted

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

基于Setter的依赖注入

在调用无参数构造函数或无参数static工厂方法来实例化bean之后,基于Setter的 DI通过调用bean上的容器调用setter方法来完成

以下示例显示了一个只能使用纯setter注入进行依赖注入的类。这个类是传统的Java。这是一个POJO,它不依赖于容器特定的接口,基类或注释。

public class SimpleMovieLister {

  // SimpleMovieLister对MovieFinder有依赖性
  private MovieFinder movieFinder;

  //一个setter方法,以便Spring容器可以'注入'一个MovieFinder
  public void setMovieFinder(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }

  //实际“使用”注入的MovieFinder的业务逻辑被省略。
}

ApplicationContext支持它所管理的bean的基于构造函数和基于setter的DI。它也支持基于setter的DI之后,通过构造函数方法已经注入了一些依赖项。你在的形式配置的依赖关系BeanDefinition,这与你使用的PropertyEditor情况下,将属性从一种格式转换为另一种。然而,大多数Spring用户不直接使用这些类(以编程方式),而是使用XML定义文件,然后将其内部转换为这些类的实例,并用于加载整个Spring IoC容器实例。

基于构造函数或基于setter的DI?

既然你可以混合使用基于构造函数和基于Setter的DI,那么使用强制依赖性的构造函数参数和可选依赖性的设置符是一个很好的经验法则。请注意,在 setter上使用@Required注释可以用来使setter需要依赖关系。

Spring团队通常主张setter注入,因为大量的构造函数参数可能会变得笨拙,特别是当属性是可选的时候。Setter方法也使该类的对象可以重新配置或稍后重新注入。通过JMX MBeans进行管理是一个引人注目的用例。

一些纯粹主义者喜欢基于构造函数的注入。提供所有对象依赖性意味着对象总是以完全初始化的状态返回给客户端(调用)代码。缺点是物体变得不适合重新配置和重新注入。

使用对某个班级最有意义的DI。有时,在处理您没有来源的第三方课程时,您可以选择。遗留类可能不会公开任何setter方法,因此构造函数注入是唯一可用的DI。

依赖性解决过程

该容器执行bean依赖性解析如下:

  1. 使用ApplicationContext描述所有bean的配置元数据创建和初始化。配置元数据可以通过XML,Java代码或注释来指定。

  2. 对于每个bean,如果使用该属性而不是普通构造函数,则它的依赖关系以属性,构造函数参数或静态工厂方法的参数的形式表示。当bean被实际创建时,这些依赖被提供给bean 

  3. 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。

  4. 作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够转换成字符串格式提供给所有的内置类型,比如数值 intlong, Stringboolean,等。

Spring容器在创建容器时验证每个bean的配置,包括验证bean引用属性是否引用有效的bean。但是,在实际创建 bean之前,bean属性本身不会被设置Beans是单身作用域并且被设置为预先实例化的(默认的)是在创建容器时创建的。范围在第5.5节“Bean范围”中定义。否则,只有在请求时才创建bean。创建一个bean可能会导致创建一个bean图,因为bean的依赖关系及其依赖关系的依赖关系(等等)被创建和分配。

通常你可以相信春天做正确的事情。它在容器加载时检测配置问题,比如引用不存在的bean和循环依赖关系。当bean实际创建时,Spring会尽可能晚地设置属性并解决依赖关系。这意味着,如果在创建该对象或其某个依赖关系时遇到问题,那么请求对象时,正确加载的Spring容器可能会稍后生成异常。例如,由于缺少或无效的属性,bean抛出异常。某些配置问题的可能延迟可见性是原因ApplicationContext 实现默认预先实例化单例bean。在实际需要这些bean之前,为了创建这些bean需要一定的时间和内存,您ApplicationContext会在创建时发现配置问题 ,而不是稍后。您仍然可以重写此默认行为,以便单例bean将会进行延迟初始化,而不是预先实例化。

如果不存在循环依赖关系,则当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean都被注入到依赖bean 之前完全配置。这意味着如果bean A对bean B有依赖性,Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是预先实例化的单例),它的将设置依赖关系,并调用相关的生命周期方法(例如配置的init方法InitializingBean回调方法)。

依赖注入的例子

以下示例使用基于设置者的DI的基于XML的配置元数据。Spring XML配置文件的一小部分指定了一些bean定义:

<bean id="exampleBean" class="examples.ExampleBean">

<!-- setter injection using the nested <ref/> element -->
<property name="beanOne"><ref bean="anotherExampleBean"/></property>

<!-- setter injection using the neater 'ref' attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
  }
}

在前面的例子中,setters被声明为与XML文件中指定的属性相匹配。以下示例使用基于构造函数的DI:

<bean id="exampleBean" class="examples.ExampleBean">

<!-- constructor injection using the nested <ref/> element -->
<constructor-arg>
  <ref bean="anotherExampleBean"/>
</constructor-arg>

<!-- constructor injection using the neater 'ref' attribute -->
<constructor-arg ref="yetAnotherBean"/>

<constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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定义中指定的构造函数参数将用作参数的构造函数 ExampleBean

现在考虑一下这个例子的一个变体,在这里不是使用构造函数,而是告诉Spring调用一个static工厂方法来返回对象的一个​​实例:

<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 {

  // a private constructor
  private ExampleBean(...) {
    ...
  }
  
  // a static factory method; the arguments to this method can be
  // considered the dependencies of the bean that is returned,
  // regardless of how those arguments are actually used.
  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属性),因此在此不讨论细节。

5.4.2详细的依赖和配置

如前一节所述,您可以将bean属性和构造函数参数定义为对其他托管bean(协作者)的引用,或者将它们定义为内联定义的值。Spring的基于XML的配置元数据为此支持其元素<property/>和 <constructor-arg/>元素中的子元素类型 

直线值(基元,Strings等等)

value所述的属性 <property/>元素指定属性或构造器参数的人类可读的字符串表示。如前所述,JavaBeans PropertyEditors用于将这些字符串值从a转换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"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>

以下示例使用p-namespace进行更简洁的XML配置。

<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close"
      p:driverClassName="com.mysql.jdbc.Driver"
      p:url="jdbc:mysql://localhost:3306/mydb"
      p:username="root"
      p:password="masterkaoli"/>

</beans>

前面的XML更简洁;然而,错字是在运行时而不是设计时发现的,除非您在创建bean定义时使用支持自动属性完成的IDE(例如IntelliJ IDEA或SpringSource Tool Suite(STS))。强烈建议这种IDE帮助。 您还可以将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容器通过使用JavaBeans 机制<value/>元素内部的文本 转换为 java.util.Properties实例PropertyEditor这是一个很好的捷径,它是Spring团队赞成<value/>value属性样式上使用嵌套元素的少数几个地方之一

idref元素

idref元素只是一种防错的方式,将容器中另一个bean id(字符串值 - 不是引用)传递给 <constructor-arg/>or <property/>元素。

<bean  id = “theTargetBean”  class = “...” />

<bean  id = “theClientBean”  class = “...” > 
  <property  name = “targetName” > 
      <idref  bean = “theTargetBean” /> 
  </ property> 
</ bean>

上面的bean定义片段 与下面的片段完全等价(在运行时):

<bean  id = “theTargetBean”  class = “...” />

<bean  id = “client”  class = “...” > 
  <property  name = “targetName”  value = “theTargetBean” /> 
</ bean>

 

第一种形式比第二种形式更可取,因为使用 idref标签允许容器在部署时验证 所引用的命名bean实际存在。在第二种变体中,不会对传递给bean targetName属性 的值执行验证 clientclientbean实际实例化时,只会发现错误(最有可能致命的结果)如果这个clientbean是一个 原型 bean,那么这个错字和产生的异常可能只会在容器被部署后很长时间才被发现。

此外,如果被引用的bean位于同一个XML单元中,并且bean名称是bean id,则可以使用该 local属性,该属性允许XML解析器本身在XML文档分析时更早地验证bean id。

 
<property  name = “targetName” > 
 <! - 一个ID为'theTargetBean'的bean必须存在; 否则会抛出异常 - >
  <idref  local = “theTargetBean” /> 
</ property>

其中<IDREF />元素带来值A共同的地方(至少在早期比Spring 2.0版本)是在配置AOP拦截在 ProxyFactoryBeanbean定义。指定拦截器名称时使用<idref />元素可以防止拼写错误拦截器ID。

参考其他豆类(协作者)

ref元素是一个<constructor-arg/>或 <property/>定义元素中的最后一个 元素。在这里,您将bean的指定属性的值设置为对由容器管理的另一个bean(协作者)的引用。被引用的bean是其属性将被设置的bean的依赖项,并且在属性设置之前根据需要初始化它。(如果协作者是单身bean,它可能已被容器初始化。)所有引用最终都是对另一个对象的引用。划定范围和有效性取决于是否通过指定其他对象的ID /名称 beanlocal,或 parent属性。

通过标记bean 属性指定目标bean <ref/>是最通用的形式,并且允许创建对同一个容器或父容器中的任何bean的引用,而不管它是否位于同一个XML文件中。bean属性的值可能id与目标bean 属性相同,或者与目标bean属性中的值之一相同name

<ref  bean = “someBean” />

通过local 属性指定目标bean 利用XML解析器验证同一文件中的XML id引用的能力。local属性的值 必须id与目标bean 属性相同 如果在同一文件中找不到匹配的元素,则XML解析器会发出错误。因此,如果目标bean位于同一个XML文件中,则使用本地变体是最佳选择(以便尽可能早地了解错误)。

<ref  local = “someBean” />

通过parent 属性指定目标Bean 将创建对当前容器的父容器中的bean的引用。parent 属性的值可能id与目标bean 属性或目标bean 属性中的一个值相同name,并且目标bean必须位于当前bean的父容器中。您主要在具有容器层次结构时使用此bean参考变体,并且想要使用与父bean名称相同的代理将父容器中的现有bean包装在父容器中。

<! - 在父上下文中 - >
 <bean  id = “accountService”  class = “com.foo.SimpleAccountService” > 
  <! - 根据需要插入依赖关系 - >
 </ bean>



<! - 在子(后代)上下文中 - >
 <bean  id = “accountService”   < -  bean名称与父bean相同 - >
    类= “的org.springframework.aop.framework.ProxyFactoryBean”>
    <property  name = “target” > 
        <ref  parent = “accountService” />   <! - 注意我们如何引用父bean  - >
     </ property> 
  <! - 根据需要插入其他配置和依赖关系 - >
 </ bean>

内置bean

<bean/>内部的元件 <property/>或 <constructor-arg/>元件定义了一个所谓的 内部bean

<bean  id = “outer”  class = “...” > 
<! - 而不是使用对目标bean的引用,只需定义目标bean inline  - >
 <property  name = “target” > 
  <bean  class = “com.example.Person” >  <! - 这是内部bean  - >
     <property  name = “name”  value = “Fiona Apple” /> 
    <property  name = “age”  value = “25” /> 
  < / bean> 
</ property> 
</ bean>

内部bean定义不需要定义的id或名称; 该容器忽略这些值。它也忽略了 scope国旗。内部bean 始终是匿名的,并且它们 始终使用外部bean创建。这是 不是可以内部bean注入到协作不是进入封闭豆等豆类。

集合

<list/>, <set/><map/>,和 <props/>元素,你将Java的性能和参数Collection类型 ListSet, Map,和 Properties分别。

<bean  id = “moreComplexObject”  class = “example.ComplexObject” > 
<! - 导致setAdminEmails(java.util.Properties)调用 - >
 <property  name = “adminEmails” > 
  <props> 
      <prop  key = “管理员“ > administrator@example.org </ prop> 
      <prop  key = ”support“ > support@example.org </ prop> 
      <prop  key = ”development“ > development@example.org </ prop>
  </ props> 
</ property>
<! - 产生一个setSomeList(java.util.List)调用 - >
 <property  name = “someList” > 
  <list> 
      <value>一个列表元素,后跟一个引用 </ value> 
      <ref  bean = “myDataSource /> 
  </ list> 
</ property> 
<! - 导致setSomeMap(java.util.Map)调用 - >
 <property  name = ”someMap“ > 
  <map> 
      <entry  key = ”entry“  value = “只是一些字符串” /> 
      <entry  key = “a ref”  value-ref = “myDataSource”/> 
  </ map> 
</ property> 
<! - 导致setSomeSet(java.util.Set)调用 - >
 <property  name = “someSet” > 
  <set> 
      <value>只是一些字符串</ value> 
      <ref  bean = “myDataSource” /> 
  </ set> 
</ property> 
</ bean>

映射键或值或设置值的值也可以是以下任何元素:

bean | ref | idref | 列表| set | 地图| 道具| 值| 空值
集合合并

从Spring 2.0开始,容器支持 合并集合。应用程序开发人员可以定义一个父风格<list/>, <map/><set/>或 <props/>元素,并有孩子式的 <list/><map/><set/><props/> 元素继承和父集合覆盖值。也就是说,子集合的值是合并父集合和子集合元素的结果,子集合元素覆盖父集合中指定的值。

这部分关于合并讨论了父子bean机制。不熟悉父代和子代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>

注意在bean定义merge=true属性的<props/>元素上使用 adminEmails属性 child当 childbean被容器解析并实例化时,生成的实例具有一个adminEmailsProperties 集合,该 集合包含合并子集合 adminEmails与父 adminEmails集合的结果。

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

孩子Properties集合的值设置继承父所有属性元素 <props/>,和孩子的为值 support值将覆盖父集合的价值。

这一合并行为同样适用于 <list/><map/>和 <set/>集合类型。<list/>元素的特定情况下,List集合类型相关联的语义(即ordered值集合的概念)被保留; 父项的值在所有子项列表的值之前。在的情况下Map, Set和 Properties集合类型,没有顺序存在。因此,没有排序的语义在背后的关联的集合类型的效果 MapSet以及 Properties该容器内部使用实现类型。

收集合并的限制

您不能合并不同的集合类型(如a Map和a List),并且如果您确实尝试这样做,Exception则会引发适当的集合类型 该 merge属性必须在较低的继承的子定义上指定; merge 在父集合定义上指定属性是多余的,并且不会导致所需的合并。合并功能仅在Spring 2.0及更高版本中可用。

强类型集合(仅限Java 5+)

在Java 5及更高版本中,您可以使用强类型集合(使用泛型类型)。也就是说,可以声明一个 Collection只能包含String元素的类型(例如)。如果您使用Spring将强类型依赖注入 Collection到bean中,则可以利用Spring的类型转换支持,以便强类型Collection 实例的元素在添加到类型之前转换为适当的类型Collection

 

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>

bean accounts属性 foo准备注入时,有关强类型的元素类型的泛型信息 Map<String, Float>可通过反射获得。因此,Spring的类型转换基础结构将各种值元素识别为类型 Float和字符串值9.99, 2.75,并将3.99其转换为实际Float类型。

空和空字符串值

春天将属性等的空论据视为空白 Strings以下基于XML的配置元数据片段将email属性设置为空 String值(“”)

<bean  class = “ExampleBean” > 
<property  name = “email”  value = “” /> 
</ bean>

前面的示例等同于以下Java代码: exampleBean.setEmail("")该 <null/>元素处理null 值。例如

<bean  class = “ExampleBean” > 
<property  name = “email” > <null /> </ property> 
</ bean>

以上配置相当于以下Java代码: exampleBean.setEmail(null)

带有p命名空间的XML快捷方式

p-名称空间使您可以使用bean 元素的属性(而不是嵌套 <property/>元素)来描述属性值和/或合作bean。

Spring 2.0和更高版本支持带有命名空间的可扩展配置格式,这些命名空间基于XML模式定义。beans本章讨论配置格式在XML Schema文档中定义。但是,p-namespace并未在XSD文件中定义,而只存在于Spring的核心中。

以下示例显示了解析为相同结果的两个XML片段:第一个使用标准XML格式,第二个使用p命名空间。

<beans  xmlns = “http://www.springframework.org/schema/beans” 
  xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance” 
  xmlns:p = “http:// www .springframework.org / schema / p“ 
  xsi:schemaLocation = ”http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd“ > 

  <bean  name = “classic”  class = “com.example.ExampleBean” > 
      <property  name = “email”  value = “ foo@bar.com ”/> 
  </ bean> 

  <bean name = “p-namespace”  class = “com.example.ExampleBean” 
        p:email = “ foo@bar.com ” /> 
</ beans>

该示例在bean定义中显示了名为email的p名称空间中的一个属性。这告诉Spring包含一个属性声明。如前所述,p-名称空间没有模式定义,因此您可以将该属性的名称设置为属性名称。

下一个示例包含两个更多的bean定义,这两个定义都可以引用另一个bean:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="john-classic" class="com.example.Person">
      <property name="name" value="John Doe"/>
      <property name="spouse" ref="jane"/>
  </bean>

  <bean name="john-modern"
      class="com.example.Person"
      p:name="John Doe"
      p:spouse-ref="jane"/>

  <bean name="jane" class="com.example.Person">
      <property name="name" value="Jane Doe"/>
  </bean>
</beans>

 

正如你所看到的,这个例子不仅包含使用p-命名空间的属性值,还使用特殊格式来声明属性引用。第一个bean定义用于 <property name="spouse" ref="jane"/>创建从bean john到bean 的引用jane,而第二个bean定义 p:spouse-ref="jane"用作属性来完成同样的事情。在这种情况下spouse是属性名称,而该-ref部分表明这不是一个正值,而是对另一个bean的引用。

[注意]

p-名称空间不如标准XML格式那么灵活。例如,声明属性引用的格式与结尾的属性发生冲突Ref,而标准的XML格式则不会。我们建议您谨慎选择您的方法,并将其传达给您的团队成员,以避免生成同时使用所有三种方法的XML文档。

带有c-namespace的XML快捷方式

类似于“带有p-namespace的XML快捷方式”一节,Spring 3.1中新引入c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套constructor-arg 元素。

让我们回顾一下从例子中称为“基于构造函数的依赖注入”一节c命名空间:

 

<beans xmlns = “http://www.springframework.org/schema/beans” 
  xmlns:xsi = “http://www.w3.org/2001/XMLSchema-instance” 
  xmlns:c = “http:// www .springframework.org /模式/ C” 
  的xsi:的schemaLocation = “http://www.springframework.org/schema/beans
       HTTP://www.springframework.org/schema/beans/spring-beans.xsd”>
 
  <bean id = “bar”  class = “xyBar” /> 
  <bean id = “baz”  class = “xyBaz” /> 

  < - 'traditional'declaration - > 
  <bean id = “foo” class = “xyFoo” >
      <constructor-arg ref = “bar” /> 
      <constructor-arg ref = “baz” /> 
      <constructor-arg value = “ foo@bar.com ” /> 
  </ bean> 

  < - 'c-namespace'声明- > 
  <bean id = “foo”  class = “xyFoo” c:bar-ref = “bar” c:baz-ref = “baz” c:email = “ foo@bar.com ” > 

</ beans>

c:命名空间使用相同的约定作为p:一个(后-ref为bean引用),供他们的名字设置构造函数的参数。同样,即使它没有在XSD模式中定义(但它存在于Spring内核中),也需要声明它。

对于构造函数参数名称不可用的罕见情况(通常如果字节码是在没有调试信息的情况下编译的),可以使用回退参数索引:

< - 'c-namespace'索引声明 - > 
<bean id = “foo”  class = “xyFoo” c:_ 0 -ref = “bar” c:_ 1 -ref = “baz” >
[注意]

由于XML语法,索引表示法要求存在前导_,因为XML属性名称不能以数字开头(即使某些IDE允许)。

实际上,构造器解析机制在匹配参数方面非常有效,所以除非真的需要,否则我们建议在整个配置中使用名称符号。

复合属性名称

在设置bean属性时,只要最终属性名称以外的路径的所有组件都不是,就可以使用复合或嵌套属性名称null考虑下面的bean定义。

<bean  id = “foo”  class = “foo.Bar” > 
<property  name = “fred.bob.sammy”  value = “123” /> 
</ bean>

foobean有一个fred 属性,该属性具有一个属性,该bob属性具有 sammy属性,并且该最终 sammy属性被设置为该值 123为了使这一工作, fred财产foobob财产fred绝不能 null豆后构造,或 NullPointerException抛出。

5.4.3使用 depends-on

如果一个bean是另一个bean的依赖,那通常意味着一个bean被设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中<ref/> 元素完成此操作 但是,有时豆类之间的依赖性不那么直接; 例如,类中的静态初始化程序需要被触发,例如数据库驱动程序注册。depends-on使用此元素的bean被初始化之前,属性可以明确地强制一个或多个bean被初始化。以下示例使用该 depends-on属性来表示对单个bean的依赖关系:

<bean  id = “beanOne”  class = “ExampleBean”  depends-on = “ manager ”/> <bean id = “ manager ”class =“ManagerBean”/>

要表示对多个bean的依赖关系,请提供一个bean名称列表作为depends-on属性的值,并使用逗号,空格和分号作为有效分隔符

<bean  id = “beanOne”  class = “ExampleBean”  depends-on = “manager,accountDao” > 
<property  name = “manager”  ref = “manager” /> 
</ bean> 

<bean  id = “manager”  class = “ ManagerBean“ /> 
<bean  id = ”accountDao“  class = ”xyjdbc.JdbcAccountDao“ />

 

[注意]

depends-onbean定义中属性可以指定一个初始化时间依赖关系,并且在只有singleton bean 的情况下可以指定 一个相应的销毁时间依赖关系。定义depends-on与给定bean 关系的依赖bean在销毁给定bean之前首先被销毁。因此depends-on也可以控制关​​机顺序。

5.4.4懒惰初始化的bean

默认情况下, ApplicationContext 实现急切地创建和配置所有的singleton bean作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后。当发生这种情况是理想的,你可以通过标记bean定义为延迟初始化,防止单豆的预实例化。一个惰性初始化bean告诉IoC容器在第一次请求时创建一个bean实例,而不是在启动时。

在XML中,此行为由元素lazy-init上的属性 控制 <bean/>例如:

 

<bean  id = “lazy”  class = “com.foo.ExpensiveToCreateBean”  lazy-init =“true” /> <bean name = “not.lazy” class = “com.foo.AnotherBean” />

当前面的配置被an消费时 ApplicationContext,名为bean的bean lazyApplicationContext启动时并没有被急切地预先实例化 ,而not.lazybean被急切地预先实例化。

然而,当一个懒惰初始化bean是一个未经过延迟初始化的单例bean的依赖时,它 ApplicationContext会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。懒惰初始化的bean被注入一个单独的bean中,并且没有被初始化。

您还可以通过使用元素default-lazy-init上的属性 来控制容器级别的延迟初始化<beans/>例如:

<beans  default-lazy-init = “true” > 
  <! - 没有bean将被预先实例化 - >
 </ beans>

5.4.5自动装配合作者

Spring容器可以自动连接合作bean之间的关系。您可以允许Spring通过检查Bean的内容来自动为bean解析协作者(其他bean)ApplicationContext自动装配具有以下优点:

  • 自动装配可以显着减少指定属性或构造函数参数的需要。(其他机制,例如本章其他地方讨论的bean模板 在这方面也很有价值。)

  • 随着对象的发展,自动装配可以更新配置。例如,如果需要向类中添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发过程中可能特别有用,而且不会影响代码库变得更加稳定时切换到显式布线的选项。

当使用基于XML的配置元数据[2]时,可以使用元素autowire属性为 bean定义指定autowire模式 <bean/>自动装配功能有五种模式。您可以指定每个 bean的自动装配,因此可以选择自动装配的自动装配。

表5.2。自动装配模式

模式说明
没有

(默认)无自动装配。Bean引用必须通过一个ref元素定义建议不要将更改默认设置用于较大的部署,因为指定的协作者明确提供了更好的控制和清晰度。它在一定程度上记录了系统的结构。

昵称

按物业名称自动装配。Spring会查找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为autowire的名称,并且它包含一个属性(也就是说,它有一个 setMaster(..)方法),Spring会查找名为的bean定义master,并使用它来设置属性。

byType的

如果属性类型中只有一个bean存在于容器中,则允许属性为自动装配。如果存在多于一个,则会引发致命异常,这表明您可能不会为该bean 使用byType自动装配。如果没有匹配的bean,则什么都不会发生; 该物业未设置。

constructor

类似于byType,但适用于构造函数参数。如果容器中不存在唯一的构造函数参数类型的bean,则会引发致命错误。

 

使用byType构造函数 自动装配模式,您可以连线阵列和类型集合。在这种情况下 所有被提供在所述容器内自动装配候选匹配所期望的类型,为了满足的依赖性。如果预期的密钥类型是,则可以自动装入强类型的地图 String自动装配的Maps值将由所有与预期类型匹配的bean实例组成,Maps键将包含相应的bean名称。

您可以将autowire行为与依赖性检查结合起来,这是在自动装配完成后执行的。

自动装配的局限和缺点

自动装配在项目中一致使用时效果最佳。如果通常不使用自动装配,开发人员可能会使用它来仅连接一个或两个bean定义。

考虑自动装配的局限性和缺点:

  • 显式依赖关系property和 constructor-arg设置总是覆盖自动装配。您不能自动调用所谓的 简单属性,如基元 Strings,和Classes (以及这种简单属性的数组)。这个限制是通过设计。

  • 自动装配不如准确布线。虽然,如上表所述,Spring在注意避免猜测可能会有意想不到的结果的情况下进行猜测,但您的Spring管理的对象之间的关系不再明确记录。

  • 布线信息可能无法用于可能从Spring容器生成文档的工具。

  • 容器中的多个bean定义可以匹配由setter方法或构造函数参数指定的类型以进行自动装配。对于数组,集合或地图,这不一定是个问题。然而,对于期望单一值的依赖关系,这种不明确性不是任意解决的。如果没有唯一的bean定义可用,则抛出异常。

在后一种情况下,您有几种选择:

  • 放弃自动布线以支持显式布线。

  • 通过设置其autowire-candidate属性, 避免为bean定义自动装配, false如下一节所述。

  • 通过将元素 属性 设置为,将单个bean定义指定为 主要候选者 primary<bean/>true

  • 如果您使用的是Java 5或更高版本,请参见第5.9节“基于注释的容器配置”中所述实现基于注释的配置可用的更细粒度的控制

从自动装配中排除bean

在每个bean的基础上,您可以从自动装配中排除一个bean。在Spring的XML格式中,设置元素autowire-candidate 属性<bean/>为 false容器使该特定的bean定义对自动装配基础结构不可用(包括注释样式配置等@Autowired)。

您还可以根据与bean名称的模式匹配来限制自动导向候选项。顶级<beans/> 元素在其default-autowire-candidates属性中接受一个或多个模式 例如,要将autowire候选者状态限制为名称以存储库结尾的任何bean ,请提供值* Repository。要提供多种模式,请在逗号分隔列表中定义它们。 bean定义属性的显式值true或者false对于bean定义autowire-candidate属性的显式值总是优先的,对于这样的bean,模式匹配规则不适用。

这些技术对于不想通过自动装配注入其他bean的bean非常有用。这并不意味着排除的bean本身不能使用自动装配进行配置。相反,该bean本身不是自动装配其他bean的候选者。

5.4.6方法注入

在大多数应用场景中,容器中的大部分bean都是单例当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖。当bean生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。每次需要时,容器都不能向bean A提供bean B的新实例。

解决方案是放弃一些控制反转。您可以通过实现 接口让bean A知道容器ApplicationContextAware,并且每当bean A需要时,通过对容器的getBean(“B”)调用请求(通常是新的)bean B实例。以下是这种方法的一个例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

 private ApplicationContext applicationContext;

 public Object process(Map commandState) {
    // grab a new instance of the appropriate Command
    Command command = createCommand();
    // set the state on the (hopefully brand new) Command instance
    command.setState(commandState);
    return command.execute();
 }

 protected Command createCommand() {
    // notice the Spring API dependency!
    return this.applicationContext.getBean("command", Command.class);
 }

 public void setApplicationContext(ApplicationContext applicationContext)
                                                                  throws BeansException {
    this.applicationContext = applicationContext;
 }
}

前面的内容是不可取的,因为业务代码知道并且耦合到Spring框架。方法注入是Spring IoC容器的一个高级特性,它允许以干净的方式处理这个用例。

查找方法注入

查找方法注入是容器覆盖容器管理的bean上方法的能力,以返回容器中另一个命名bean的查找结果。查找通常包含一个原型bean,如前一节所述。Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现了此方法注入。

[注意]

为了使这个动态子类工作,Spring容器将继承的类不能成为final,并且被覆盖的方法也不能final此外,测试具有abstract方法的类需要您自己对该类进行子类化并提供方法的存根实现abstract最后,已经成为方法注入目标的对象不能被序列化。从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类在org.springframework下重新打包并在Spring-Core JAR中分发。这是为了方便起见以及避免与使用不同版本的CGLIB的其他项目的潜在冲突。

查看CommandManager前面的代码片段中的类,你会发现Spring容器会动态地覆盖该createCommand()方法的实现 您的 CommandManager类将不会有任何Spring依赖关系,如在重新构造的示例中所示:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>

在包含要注入的方法的客户端类(在这种情况下为CommandManager)中,要注入的方法需要以下形式的签名: <public | protected> [abstract] <return-type> theMethodName(no-arguments); 如果方法是抽象的,则动态生成的子类将实现该方法。否则,动态生成的子类会覆盖原始类中定义的具体方法。例如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>

在包含要注入的方法的客户类( CommandManager在这种情况下)中,要注入的方法需要以下形式的签名:

<public | protected> [abstract] <return-type> theMethodName(no-arguments);

如果该方法是abstract,则动态生成的子类将实现该方法。否则,动态生成的子类会覆盖原始类中定义的具体方法。例如

<! - 作为原型部署的有状态bean(非单
实例) - > <bean  id = “command”  class = “fiona.apple.AsyncCommand”  scope = “prototype” > 
<! - 根据需要在这里注入依赖关系- >
 </ bean>

<! -  commandProcessor使用statefulCommandHelper  - >
 <bean  id = “commandManager”  class = “fiona.apple.CommandManager” > 
<lookup-method  name = “createCommand”  bean = “command” /> 
</ bean>

标识为commandManager的bean createCommand()在需要命令 bean 的新实例时调用其自己的方法你必须小心地将commandbean 部署为原型,如果这实际上是需要的话。如果它作为单例部署command则每次都返回同一个bean 实例

[小费]

感兴趣的读者也可以找到 ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包中)使用。ServiceLocatorFactoryBean中使用的方法与另一个实用程序类相似 ObjectFactoryCreatingFactoryBean,但它允许您指定自己的查找界面,而不是Spring特定的查找界面。有关这些类的JavaDocs以及此博客条目,请参阅ServiceLocatorFactoryBean的其他信息。

任意方法替换

一种比查找方法更少用的方法注入形式注入能够用另一种方法实现来替换托管bean中的任意方法。在实际需要功能之前,用户可以安全地跳过本节的其余部分。

使用基于XML的配置元数据,您可以使用该 replaced-method元素将现有的方法实现替换为已部署的bean。考虑下面的类,我们想要覆盖一个方法computeValue:

public class MyValueCalculator {

public String computeValue(String input) {
  // some real code...
}

// some other methods...

}

实现org.springframework.beans.factory.support.MethodReplacer 接口的类 提供了新的方法定义。

/** meant to be used to override the existing computeValue(String)
  implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {

  public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
      // get the input value, work with it, and return a computed result
      String input = (String) args[0];
      ...
      return ...;
  }
}

 

部署原始类并指定方法覆盖的bean定义如下所示:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">

<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
  <arg-type>String</arg-type>
</replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

 

您可以使用<arg-type/>元素中的一个或多个包含 元素 <replaced-method/>来指示被覆盖的方法的方法签名。只有当方法过载并且类中存在多个变体时,参数的签名才是必需的。为了方便,参数的类型字符串可能是完全限定类型名称的子字符串。例如,以下全部匹配 java.lang.String

    java.lang.String 
  String 
  Str

由于参数的数量通常足以区分每种可能的选择,因此只需键入与参数类型匹配的最短字符串,此快捷键就可以节省大量输入。

 

 

 

 

 

posted @ 2018-04-09 17:47  NiceCui  阅读(573)  评论(0编辑  收藏  举报

个人博客主页