Spring官方文档阅读(二)之IOC容器(依赖注入)

4.依赖

典型的企业应用程序不包含单个对象(或Spring术语中的Bean)。 即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。 下一部分将说明如何从定义多个独立的Bean定义到实现对象协作以实现目标的完全实现的应用程序。

4.1依赖注入

依赖注入(DI)是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象)。 从工厂方法返回。 然后,容器在创建Bean时注入那些依赖项。 从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来控制Bean自身依赖关系的实例化或定位的Bean本身的逆过程(因此称为控制反转)。

使用DI原理,代码更简洁,当为对象提供依赖项时,去耦合会更有效。 该对象不查找其依赖项,也不知道依赖项的位置或类。 结果,您的类变得更易于测试,尤其是当依赖项依赖于接口或抽象基类时,它们允许在单元测试中使用存根或模拟实现。

依赖注入有两种主要的变体:基于构造器的依赖注入和基于Setter的依赖注入。

4.11基于构造函数的依赖注入

基于构造函数的DI是通过容器调用具有多个参数(每个参数代表一个依赖项)的构造函数来完成的。 调用带有特定参数的静态工厂方法来构造Bean几乎是等效的,并且本次讨论将构造函数和静态工厂方法的参数视为类似。 以下示例显示了只能通过构造函数注入进行依赖项注入的类:

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...
}

请注意,这个类没有什么特殊之处。它是一个POJO,不依赖于容器特定的接口、基类或注释。

构造函数参数解析

构造函数参数解析匹配通过使用参数的类型进行。 如果Bean定义的构造函数参数中不存在潜在的歧义,则在实例化Bean时,在Bean定义中定义构造函数参数的顺序就是将这些参数提供给适当的构造函数的顺序。 考虑以下类别:

package x.y;
public class ThingOne {
  
  public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
    // ...
  }
  
}

假设ThingTwoThingThree类没有通过继承关联,则不存在潜在的歧义。 因此,以下配置可以正常工作,并且您无需在元素中显式指定构造函数参数索引或类型。

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

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

package examples;

public class ExampleBean {
  
  // Number of years to 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>

除了解决多个简单值的歧义性之外,指定索引还可以解决歧义,其中构造函数具有两个相同类型的参数。

构造函数参数的名字

您还可以使用构造函数参数名称来消除歧义,如以下示例所示:

<bean id="exampleBean" class="examples.ExampleBean">
  <constructor-arg name="years" value="7500000"/>
  <constructor-arg name="ultimateAnswer" value="42"/>
bean>

请记住,要立即使用该功能,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。 如果您不能或不想使用debug标志编译代码,则可以使用@ConstructorProperties JDK注解显式命名构造函数参数。 然后,该示例类必须如下所示:

package examples;

public class ExampleBean {
  // Fields omitted

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

4.12基于Setter的依赖注入

基于setterDI是由容器在调用无参数构造函数或无参数静态工厂方法来实例化Bean之后调用Bean上的setter方法来完成的。

下面的示例显示只能通过使用纯setter注入来依赖注入的类。 此类是常规的Java。 它是一个POJO,不依赖于容器特定的接口,基类或注解。

public class SimpleMovieLister {
  
  // the SimpleMovieLister has a dependency on the MovieFinder
  private MovieFinder movieFinder;

  // a setter method so that the Spring container can inject a MovieFinder
  public void setMovieFinder(MovieFinder movieFinder) {
    this.movieFinder = movieFinder;
  }

  // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext支持它管理的Bean的基于构造函数和基于setter的依赖注入。 在已经通过构造函数方法注入了某些依赖项之后,它还支持基于setter的依赖注入。 您可以以BeanDefinition的形式配置依赖项,并与PropertyEditor实例结合使用以将属性从一种格式转换为另一种格式。 但是,大多数Spring用户并不直接(即以编程方式)使用这些类,而是使用XML bean定义,带注解的组件(即以@ Component,@ Controller等进行注释的类)或@Bean方法来处理这些类。 基于Java的@Configuration类。 然后将这些源在内部转换为BeanDefinition实例,并用于加载整个Spring IOC容器实例。

如何选择?

由于可以混合使用基于构造函数的DI和基于setter的DI,因此将构造函数用于强制性依赖项并将setter方法或配置方法用于可选依赖性是一个很好的经验法则。 注意,可以在setter方法上使用@Required批注,以使该属性成为必需的依赖项。 但是,最好使用带有参数的程序验证的构造函数注入。

Spring团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保所需的依赖项不为null。 此外,注入构造函数的组件始终以完全初始化的状态返回到客户端(调用)代码。 附带说明一下,大量的构造函数自变量是一种不好的代码味,这表明该类可能承担了太多的职责,应将其重构以更好地解决关注点分离问题。

Setter注入主要应用于可以在类中分配合理的默认值的可选依赖项。 否则,必须在代码使用依赖项的任何地方执行非空检查。 setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。 因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。

使用对特定类最有意义的DI样式。有时,在处理没有源代码的第三方类时,需要自己做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

4.13依赖解析的过程

容器执行以下bean依赖项解析:

  • 使用描述所有bean的配置元数据创建和初始化ApplicationContext。 可以通过XML,Java代码或注解来指定配置元数据。
  • 对于每个Bean,其依赖关系都以属性,构造函数参数或static-factory方法的参数的形式表示(如果使用它而不是普通的构造函数)。 在实际创建Bean时,会将这些依赖项提供给Bean。
  • 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
  • 作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,比如int、long、string、boolean等等。

在创建容器时,Spring容器验证每个bean的配置。但是,bean属性本身在实际创建bean之前是不会设置的。在创建容器时,将创建单例范围并设置为预实例化(缺省值)的bean。作用域在Bean作用域中定义。否则,只在请求bean时创建它。创建一个bean可能会导致创建一个bean图,因为创建和分配了bean的依赖项及其依赖项的依赖项(等等)。注意,解决方案之间不匹配

循环依赖

如果您主要使用构造函数注入,那么可以创建一个无法解决的循环依赖场景。

例如:A类通过构造函数注入需要B类的实例,而B类通过构造函数注入需要A类的实例。 如果您为将类A和B相互注入而配置了Bean,则Spring IOC容器会在运行时检测到该循环引用,并抛出BeanCurrentlyInCreationException。

一种可能的解决方案是编辑某些类的源代码,这些类的源代码由设置者而不是构造函数来配置。 或者,避免构造函数注入,而仅使用setter注入。 换句话说,尽管不建议这样做,但是您可以使用setter注入配置循环依赖关系。

与典型情况(没有循环依赖项)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在完全初始化之前被注入另一个bean(经典的“养鸡和鸡蛋”场景)。

通常,您可以信任Spring做正确的事。 它在容器加载时检测配置问题,例如对不存在的Bean的引用和循环依赖项。 在实际创建Bean时,Spring设置属性并尽可能晚地解决依赖关系。 这意味着如果创建对象或其依赖项之一存在问题,则正确加载的Spring容器以后可以在您请求对象时生成异常。例如,由于缺少或无效,Bean引发异常属性。 这可能会延迟某些配置问题的可见性,这就是为什么默认情况下ApplicationContext实现会预先实例化单例Bean的原因。 在实际需要这些Bean之前先花一些时间和内存来创建它们,您会在创建ApplicationContext时发现配置问题,而不是稍后。 您仍然可以覆盖此默认行为,以便单例Bean延迟初始化,而不是预先实例化。

如果不存在循环依赖关系,则在将一个或多个协作Bean注入从属Bean时,每个协作Bean都将被完全配置,然后再注入到从属Bean中。 这意味着,如果bean A依赖于bean B,则Spring IOC容器会在对bean A调用setter方法之前完全配置beanB。换句话说,实例化了bean(如果它不是预先实例化的单例) ),设置其依赖项,并调用相关的生命周期方法(例如已配置的init方法或InitializingBean回调方法)。

4.14依赖注入的例子

以下示例将基于XML的配置元数据用于基于setter的DI。 Spring XML配置文件的一小部分指定了一些Bean定义,如下所示:

<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"/>

下面的示例显示了相应的ExampleBean类:

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;
  }
}

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

<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"/>

以下示例显示了相应的ExampleBean类:

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不是使用构造函数,而是被告知调用静态工厂方法来返回对象的实例:

<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"/>

下面的示例显示了相应的ExampleBean类:

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;
  }
}

静态工厂方法的参数由元素提供,与实际使用构造函数的情况完全相同。 factory方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在此示例中为)。 实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用factory-bean属性代替class属性之外),因此在此不讨论这些细节。

4.2依赖和配置详解

如上一节所述,您可以将Bean属性和构造函数参数定义为对其他托管Bean(协作者)的引用或内联定义的值。 Spring的基于XML的配置元数据为此目的在其和元素中支持子元素类型。

4.21直接值(原语、字符串等)

元素的value属性将属性或构造函数参数指定为人类可读的字符串表示形式。 Spring的转换服务用于将这些值从字符串转换为属性或参数的实际类型。 以下示例显示了设置的各种值:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  
  <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="misterkaoli"/>
bean>

以下示例将p-命名空间用于更简洁的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
    https://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="misterkaoli"/>

beans>

前面的XML更简洁。但是,拼写错误是在运行时而不是设计时发现的,除非您使用IDE(例如IntelliJ IDEA或用于Eclipse的Spring工具)在创建bean定义时支持自动属性完成。强烈建议提供这样的IDE帮助。

您还可以配置java.util.Properties实例,如下所示:

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

  
  <property name="properties">
    <value>
      jdbc.driver.className=com.mysql.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/mydb
    value>
  property>
bean>

Spring容器通过使用JavaBeans PropertyEditor机制将元素内的文本转换为java.util.Properties实例。 这是一个不错的捷径,并且是Spring团队偏爱使用嵌套的元素而不是value属性样式的几个地方之一。

idref标签

idref标签只是一种防错方法,可以将容器中另一个bean的id(字符串值-不是引用)传递给或元素。 以下示例显示了如何使用它:

<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属性的值执行验证。 拼写错误仅在实际实例化客户端bean时发现(最有可能导致致命的结果)。 如果客户端Bean是原型Bean,则可能仅在部署容器后很长时间才发现此错字和所产生的异常。

注意:在4.0 Bean XSD中不再支持idref元素上的local属性,因为它不再提供常规Bean引用上的值。 升级到4.0模式时,将现有的idref本地引用更改为idref bean。

元素带来价值的一个常见地方(至少在Spring 2.0之前的版本中)是在ProxyFactoryBeanBean定义中的AOP拦截器的配置中。 指定拦截器名称时使用元素可防止您拼写错误的拦截器ID。

对其他Bean的引用

ref元素是或定义元素内的最后一个元素。 在这里,您将一个bean的指定属性的值设置为对容器管理的另一个Bean(协作者)的引用。 引用的Bean是要设置其属性的Bean的依赖关系,并且在设置属性之前根据需要对其进行初始化。 (如果协作者是单例Bean,则它可能已经由容器初始化了。)所有引用最终都是对另一个对象的引用。 范围和验证取决于您是通过Bean还是父属性指定另一个对象的ID或名称。

通过标记的Bean属性指定目标Bean是最通用的形式,并且允许创建对同一容器或父容器中任何Bean的引用,而不管它是否在同一XML文件中。 Bean属性的值可以与目标Bean的id属性相同,也可以与目标Bean的name属性中的值之一相同。 下面的示例演示如何使用ref元素:

<ref bean="someBean"/>

通过parent属性指定目标Bean将创建对当前容器的父容器中的Bean的引用。 parent属性的值可以与目标bean的id属性或目标bean的name属性中的值之一相同。 目标Bean必须位于当前容器的父容器中。 主要在具有容器层次结构并且要使用与父bean名称相同的代理将现有bean封装在父容器中时,才应使用此bean参考变量。 以下清单对显示了如何使用parent属性:



  


<bean id="accountService" 
property>

bean>

注意:在4.0 bean XSD中不再支持ref元素上的local属性,因为它不再提供常规bean引用上的值。升级到4.0模式时,将现有的ref本地引用更改为ref bean。

4.23内部Bean

或元素内的元素定义了一个内部bean,如以下示例所示:

<bean id="outer" class="...">
  
  <property name="target">
    <bean class="com.example.Person"> 
      <property name="name" value="Fiona Apple"/>
      <property name="age" value="25"/>
    bean>
  property>
bean>

内部bean定义不需要定义的ID或名称。 如果指定,则容器不使用该值作为标识符。 容器在创建时也将忽略作用域标志,因为内部Bean始终是匿名的,并且始终与外部Bean一起创建。 不可能独立地访问内部bean或将其注入到协作bean中而不是封装到封闭bean中。

例如,对于包含在单例bean中的请求作用域内bean,可以从自定义作用域接收销毁回调。内部bean实例的创建被绑定到包含它的bean上,但是销毁回调让它参与到请求范围的生命周期中。这不是常见的场景。内部bean通常只是共享其包含的bean的作用域。

4.24集合

,,和元素分别设置Java集合类型List,Set,Map和Properties的属性和参数。 以下示例显示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
  
  <property name="adminEmails">
    <props>
      <prop key="administrator">[email protected]prop>
      <prop key="support">[email protected]prop>
      <prop key="development">[email protected]prop>
    props>
  property>
  
  
  <property name="someList">
    <list>
      <value>a list element followed by a referencevalue>
      <ref bean="myDataSource" />
    list>
  property>
  
  
  <property name="someMap">
    <map>
      <entry key="an entry" value="just some string"/>
      
    map>
  property>
    
  
  <property name="someSet">
    <set>
      <value>just some stringvalue>
      <ref bean="myDataSource" />
    set>
  property>
bean>

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

bean | ref | idref | list | set | map | props | value | null

集合合并

Spring容器还支持合并集合。 应用程序开发人员可以定义父,,或元素,并具有子,,或元素。 从父集合继承并覆盖值。 也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合的元素会覆盖父集合中指定的值。

下面的示例演示了集合合并:

<beans>
  <bean id="parent" abstract="true" class="example.ComplexObject">
    <property name="adminEmails">
      <props>
        <prop key="administrator">[email protected]prop>
        <prop key="support">[email protected]prop>
        props>
    property>
  bean>
  
  <bean id="child" parent="parent">
    <property name="adminEmails">
      
      <props merge="true">
        <prop key="sales">[email protected]prop>
        <prop key="support">[email protected]prop>
      props>
    property>
  bean>
<beans>

注意子bean定义的adminEmails属性的元素上使用merge = true属性。 当子bean由容器解析并实例化后,生成的实例具有adminEmails Properties集合,其中包含将孩子的adminEmails集合与父对象的adminEmails集合合并的结果。 以下清单显示了结果:

[email protected]
[email protected]
[email protected]

子属性集的值集继承了父的所有属性元素,而支持值的子值覆盖了父集合中的值。

此合并行为类似地适用于,和集合类型。 在元素的特定情况下,将维护与List集合类型关联的语义(即,值的有序集合的概念)。 父级的值先于子级列表的所有值。 对于“Map”,“List”和“Properties”集合类型,不存在任何排序。 因此,对于容器内部使用的关联Map,Set和Properties实现类型基础的集合类型,没有任何排序语义有效。

集合合并的限制

您不能合并不同的集合类型(例如Map和List)。 如果您尝试这样做,则会引发适当的Exception。 必须在下面的继承的子定义中指定merge属性。 在父集合定义上指定merge属性是多余的,不会导致所需的合并。

强类型集合

随着Java 5中泛型类型的引入,您可以使用强类型集合。 也就是说,可以声明一个Collection类型,使其只能包含(例如)String元素。 如果使用Spring将强类型的Collection依赖注入到Bean中,则可以利用Spring的类型转换支持,以便在将强类型的Collection实例的元素转换为适当的类型,然后再添加到其中。 以下Java类和bean定义显示了如何执行此操作:

public class SomeClass {
  
  private Map<String, Float> accounts;

  public void setAccounts(Map<String, Float> accounts) {
    this.accounts = accounts;
  }
}
<beans>
  <bean id="something" class="x.y.SomeClass">
    <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属性时,可以通过反射获得有关强类型Map 的元素类型的泛型信息。 因此,Spring的类型转换基础结构将各种值元素识别为Float类型,并将字符串值(9.99、2.75和3.99)转换为实际的Float类型。

4.25Null和空的字符串

Spring将属性等的空参数视为空String。 以下基于XML的配置元数据片段将email属性设置为空的String值(“”)。

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

前面的示例与下面的Java代码等效:

exampleBean.setEmail("");

元素处理空值。 以下清单显示了一个示例:

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

前面的配置相当于下面的Java代码:

exampleBean.setEmail(null);

4.26带有p-名称空间的XML快捷方式

使用p-namespace,您可以使用bean元素的属性(而不是嵌套的元素)来描述协作bean的属性值,或同时使用这两者。

Spring支持带有名称空间的可扩展配置格式,这些名称空间基于XML Schema定义。 本章讨论的bean配置格式在XML Schema文档中定义。 但是,p命名空间未在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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean name="classic" class="com.example.ExampleBean">
    <property name="email" value="[email protected]"/>
  bean>

  <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected]"/>
beans>

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

下一个示例包括另外两个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
        https://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-namespace的属性值,还使用特殊格式声明属性引用。 第一个bean定义使用创建从bean john到bean jane的引用,而第二个bean定义使用p:spouse-ref =“ jane”作为属性来执行 完全一样的东西。 在这种情况下,spouse是属性名称,而-ref部分指示这不是一个直接值,而是对另一个bean的引用。

注意:p命名空间不如标准XML格式灵活。 例如,声明属性引用的格式与以Ref结尾的属性发生冲突,而标准XML格式则没有。 我们建议您仔细选择方法,并与团队成员进行交流,以避免同时使用这三种方法生成XML文档。

4.27使用c-名称空间的XML快捷方式

与使用p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace允许使用内联属性配置构造函数参数,而不是嵌套构造函数参数元素。

以下示例使用c:命名空间执行与基于构造函数的依赖注入相同的操作:

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

  <bean id="beanTwo" class="x.y.ThingTwo"/>
  <bean id="beanThree" class="x.y.ThingThree"/>

  
  <bean id="beanOne" class="x.y.ThingOne">
    <constructor-arg name="thingTwo" ref="beanTwo"/>
    <constructor-arg name="thingThree" ref="beanThree"/>
    <constructor-arg name="email" value="[email protected]"/>
  bean>

  
  <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="[email protected]"/>

beans>

c:命名空间使用与p:相同的约定(bean引用为尾随-ref)以按名称设置构造函数参数。 同样,即使未在XSD架构中定义它(也存在于Spring核心中),也需要在XML文件中声明它。

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


<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="[email protected]"/>

注意:由于XML语法的原因,索引表示法要求出现前导,因为XML属性名不能以数字开头(尽管一些ide允许这样做)。元素也可以使用对应的索引表示法,但这种表示法并不常用,因为在这里,简单的声明顺序通常就足够了。

在实践中,构造函数解析机制在匹配参数方面非常有效,因此,除非确实需要,我们建议在整个配置中使用名称符号。

4.28复合属性名

设置bean属性时,可以使用复合属性名称或嵌套属性名称,只要路径中除最终属性名称以外的所有组件都不为空即可。 考虑以下bean定义:

<bean id="something" class="things.ThingOne">
  <property name="fred.bob.sammy" value="123" />
bean>

SomethingBean具有fred属性,该属性具有bob属性,该属性具有sammy属性,并且最终的sammy属性被设置为123的值。为了使其正常工作,something的fred属性和bob属性 构造bean后,fred的of不能为null。 否则,将引发NullPointerException。

4.3使用depends-on

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

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

要表示对多个bean的依赖关系,需要提供一个bean名称列表作为依赖属性的值(逗号、空格和分号都是有效的分隔符)

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

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

注意:依赖属性既可以指定初始化时间依赖项,也可以指定对应的销毁时间依赖项(仅在单例bean中)。在销毁给定bean本身之前,首先销毁与给定bean定义依赖关系的依赖bean。因此,依赖还可以控制关机顺序。

4.4懒加载Bean

默认情况下,作为初始化过程的一部分,ApplicationContext实现会急于创建和配置所有单例bean。 通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。 如果不希望使用此行为,则可以通过将Bean定义标记为延迟初始化来防止单例Bean的预实例化。 延迟初始化的Bean告诉IOC容器在首次请求时而不是在启动时创建一个Bean实例。

在XML中,此行为由元素上的lazy-init属性控制,如以下示例所示:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当ApplicationContext使用前面的配置时,当ApplicationContext启动时,惰性Bean不会急切地预实例化,而ApplicationContext not.lazy bean被急切地预实例化。

然而,当延迟初始化的bean是未延迟初始化的单例bean的依赖项时,ApplicationContext在启动时创建延迟初始化的bean,因为它必须满足单例的依赖项。惰性初始化的bean被注入到没有惰性初始化的其他地方的单例bean中。

您还可以通过使用元素上的default-lazy-init属性在容器级别控制延迟初始化,以下示例显示:

<beans default-lazy-init="true">
  
beans>

4.5自动装配

Spring容器可以自动装配协作bean之间的关系。 您可以通过检查ApplicationContext的内容,让Spring为您的bean自动解决协作者(其他bean)。 自动装配具有以下优点:

  • 自动装配可以大大减少指定属性或构造函数参数的需要。 (在本章其他地方讨论的其他机制,例如Bean模板,在这方面也很有价值。)
  • 自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,当代码库变得更加稳定时,无需切换到显式连接。

使用基于XML的配置元数据时,可以使用元素的autowire属性为bean定义指定自动装配模式。 自动装配功能具有四种模式。 您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。 下表描述了四种自动装配模式:

模式 解释
no (默认)无自动装配。 Bean引用必须由ref元素定义。 对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。 在某种程度上,它记录了系统的结构。
byName 按属性名称自动装配。Spring寻找与需要自动实现的属性同名的bean。例如,如果一个bean定义按名称设置为autowire,并且它包含一个master属性(也就是说,它有一个setMaster(…)方法),那么Spring将查找一个名为master的bean定义,并使用它来设置该属性。
byType 如果容器中恰好存在该属性类型的一个bean,则允许该属性自动实现。如果存在多个,就会抛出一个致命异常,这表明您不能对该bean使用byType自动装配。如果没有匹配的bean,则什么也不会发生(没有设置属性)。
constructor 类似于byType,但适用于构造函数参数。如果容器中没有构造函数参数类型的确切bean,就会引发致命错误。

使用byType或构造函数自动装配模式,您可以连接数组和类型化集合。在这种情况下,提供容器中与预期类型匹配的所有自动装配候选对象来满足依赖关系。如果期望的键类型是String,您可以自动连接强类型映射实例。自动生成的映射实例的值由与预期类型匹配的所有bean实例组成,映射实例的键包含相应的bean名称。

4.51局限性和缺点

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

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

  • 属性和构造参数设置中的显式依赖关系总是覆盖自动装配。您不能自动连接简单属性,如原语、字符串和类(以及此类简单属性的数组)。这种限制是设计出来的。
  • 自动装配不如显式装配精确。不过,正如前面的表中所指出的,Spring小心地避免猜测可能会产生意外结果的歧义。spring管理对象之间的关系不再被明确地记录。
  • 连接信息对于可能从Spring容器生成文档的工具来说可能是不可用的。
  • 容器中的多个bean定义可能与要自动实现的setter方法或构造函数参数指定的类型相匹配。对于数组、集合或映射实例,这不一定是个问题。但是,对于期望使用单个值的依赖项,这种模糊性不能任意解决。如果没有可用的唯一bean定义,则抛出异常。

在后一种场景中,您有几个选项:

  • 放弃自动装配,转而使用明确的装配。
  • 通过将其bean的autowire-candidate属性设置为false,避免自动装配bean定义。
  • 通过将其元素的primary属性设置为true,将单个bean定义指定为主要候选对象。
  • 如基于注解的容器配置中所述,使用基于注释的配置提供更细粒度的控制。

4.52从自动装配中排除Bean

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

autowire-candidate属性被设计为仅影响基于类型的自动装配。 它不会影响按名称显示的显式引用,即使未将指定的Bean标记为自动装配候选,该名称也可得到解析。 因此,如果名称匹配,按名称自动装配仍会注入Bean。

您还可以基于与Bean名称的模式匹配来限制自动装配候选。 顶级元素在其default-autowire-candidates属性内接受一个或多个模式。 例如,要将自动装配候选状态限制为名称以Repository结尾的任何bean,请提供* Repository值。 要提供多种模式,请在以逗号分隔的列表中定义它们。 Bean定义的autowire-candidate属性的显式值true或false始终优先。 对于此类bean,模式匹配规则不适用。

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

4.6方法注入

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

一个解决方案是放弃某些控制反转。 您可以通过实现ApplicationContextAware接口,并通过对容器进行getBean(“ B”)调用来使bean A知道该容器,以便每次bean A需要它时都请求一个(通常是新的)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容器的一种高级特性,它允许您干净地处理这个用例。

4.61查找方法注入

查找方法注入是容器重写容器管理的Bean上的方法并返回容器中另一个命名Bean的查找结果的能力。 查找通常涉及原型bean,如上一节中所述。 Spring框架通过使用从CGLIB库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

注意:

  • 要使这个动态子类工作,Spring bean容器子类的类不能是final,要覆盖的方法也不能是final。
  • 对具有抽象方法的类进行单元测试需要您自己对该类进行子类化,并提供该抽象方法的存根实现。
  • 具体的方法对于组件扫描也是必要的,这需要具体的类来拾取。
  • 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的 动态子类。

对于前面的代码片段中的CommandManager类,Spring容器动态地覆盖createCommand()方法的实现。 如重新编写的示例所示,CommandManager类没有任何Spring依赖项:

package fiona.apple;

// no more Spring imports!

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

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

在包含要注入的方法的客户机类中(本例中为CommandManager),要注入的方法需要以下形式的签名:

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

如果该方法是抽象的,则动态生成的子类将实现该方法。 否则,动态生成的子类将覆盖原始类中定义的具体方法。 考虑以下示例:


<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
  
bean>


<bean id="commandManager" class="fiona.apple.CommandManager">
  <lookup-method name="createCommand" bean="myCommand"/>
bean>

每当需要新的myCommand bean实例时,标识为commandManager的bean就会调用其自己的createCommand()方法。 如果确实需要将myCommand bean部署为原型,则必须小心。 如果是单例,则每次都返回myCommand bean的相同实例。

或者,在基于注解的组件模型中,您可以通过@Lookup注释声明查找方法,如下面的示例所示

public abstract class CommandManager {

  public Object process(Object commandState) {
    Command command = createCommand();
    command.setState(commandState);
    return command.execute();
  }

  @Lookup("myCommand")
  protected abstract Command createCommand();
}

或者,更惯用的是,您可以依赖于目标bean根据查找方法的声明的返回类型来解析:

public abstract class CommandManager {

  public Object process(Object commandState) {
    MyCommand command = createCommand();
    command.setState(commandState);
    return command.execute();
  }

  @Lookup
  protected abstract MyCommand createCommand();
}

请注意,通常应该使用具体的存根实现声明此类带注释的查找方法,以使其与Spring的组件扫描规则(默认情况下抽象类会被忽略)兼容。 此限制不适用于显式注册或显式导入的Bean类。

4.62任意方法替换

与查找方法注入相比,方法注入的一种不太有用的形式是能够用另一种方法实现替换托管bean中的任意方法。

对于基于xml的配置元数据,您可以使用replaced-method元素将一个已部署bean的现有方法实现替换为另一个方法实现。考虑下面的类,它有一个名为computeValue的方法,我们希望覆盖它

public class MyValueCalculator {

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

  // some other methods...
}

实现org.springframework.bean.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">
  
  <replaced-method name="computeValue" replacer="replacementComputeValue">
    <arg-type>Stringarg-type>
  replaced-method>
bean>

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

您可以在元素中使用一个或多个元素,以指示要覆盖的方法的方法签名。 仅当方法重载且类中存在多个变体时,才需要对参数签名。 为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。 例如,以下所有都匹配java.lang.String:

java.lang.String
String
Str

因为参数的数量通常足以区分每个可能的选择,所以通过让您仅键入与参数类型匹配的最短字符串,此快捷方式可以节省很多输入。

你可能感兴趣的:(Spring,spring,java)