03-Spring依赖注

依赖

注入依赖(DI)

会通过以下几种方式来实现:

  • 构造器的参数、
  • 工厂方法的参数,
  • 或给由构造函数或者工厂方法创建的对象设置属性。

因此,容器的工作就是创建bean时注入那些依赖关系

Setter注入

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

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
      //只有有set方法spring才会注入
    public void setMoveFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business(业务) logic(逻辑) that actually 'uses(用途)' the injected MovieFinder is omitted(省略)...
}

构造器注入

还可通过给静态工厂方法传参数来构造bean。接下来的介绍将认为给构造器传参与给静态工厂方法传参是类似的。

public class SimpleMovieLister {
      // the SimpleMovieLister has a dependency on the 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...
}

BeanFactory对于它所管理的bean提供两种注入依赖方式(实际上它也支持同时使用构造器注入和Setter方式注入依赖)。

处理bean依赖关系通常按以下步骤进行:
  • 根据定义bean的配置(文件)创建并初始化BeanFactory实例(大部份的Spring用户使用支持XML格式配置文件的BeanFactory或ApplicationContext实现)。
  • 每个bean的依赖将以属性、构造器参数、或静态工厂方法参数的形式出现。当这些bean被实际创建时,这些依赖也将会提供给该bean。
  • 每个属性或构造器参数既可以是一个实际的值,也可以是对该容器中另一个bean的引用。
  • 每个指定的属性或构造器参数值必须能够被转换成属性或构造参数所需的类型。默认情况下,Spring会能够以String类型提供值转换成各种内置类型,比如int、long、String、boolean等。

循环依赖

当你主要使用构造器注入的方式配置bean时,很有可能会产生循环依赖的情况。

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

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

栗子

Setter DI例子

使用<property>标签
对象使用ref
基本数据类型是用value(包括String)

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

  <!-- setter injection using the nested <ref/> element -->
  <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"/>

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-arg>
对象使用ref
基本数据类型是用value(包括String)

<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 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,返回的bean的依赖项,

    // 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-bean属性替代class属性外),因而不在此细述。

构造器参数的解析

构造器参数将根据类型来进行匹配。如果bean定义中的构造器参数类型明确,那么bean定义中的参数顺序就是对应构造器参数的顺序。考虑以下的类…

package x.y;
  public class Foo {
      public Foo(Bar bar, Baz baz) {
        // ...
    }
}

这里的参数类型非常明确(当然前提是假定类Bar与 Baz在继承层次上并无任何关系)。因此下面的配置将会很好地工作,且无须显式地指定构造器参数索引及其类型。

<beans>
    <bean name="foo" class="x.y.Foo">
        <constructor-arg>
            <bean class="x.y.Bar"/>
        </constructor-arg>
        <constructor-arg>
            <bean class="x.y.Baz"/>
        </constructor-arg>
    </bean>
</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;
    }
}

总结:
意思就是说都是引用类型,并且参数的引用类型没有层级关系的时候spring可以在注入参数的时候不写索引,会自动匹配
但是基本数据类型和String就不会

构造器参数类型匹配

针对上面的这种情况,我们可以在构造器参数定义中使用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>

使用index属性除了可以解决多个简单类型构造参数造成的模棱两可的问题之外,还可以用来解决两个构造参数类型相同造成的麻烦。注意:index属性值从0开始。

指定构造器参数索引是使用构造器IoC首选的方式。

bean属性及构造器参数详解

直接量(基本类型、Strings类型等。)

<value/>元素通过字符串来指定属性或构造器参数的值。正如前面所提到的,JavaBean PropertyEditor将用于把字符串从java.lang.String类型转化为实际的属性或参数类型。

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

idref元素(Identifier Reference)

idref元素用来将容器内其它bean的id传给<constructor-arg/><property/>元素,同时提供错误验证功能。

<bean id="theTargetBean" class="..."/>

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

上述bean定义片段完全地等同于(在运行时)以下的片段:
<idref bean="theTargetBean" /><=====> <value>theTargetBean</value>

<bean id="theTargetBean" class="..."/>

<bean id="client" class="...">
    <property name="targetName">
        <value>theTargetBean</value>
    </property>
</bean>
  • 第一种形式比第二种更可取的主要原因是,使用idref标记允许容器在部署时 验证所被引用的bean是否存在。
  • 第二种方式中,传给client bean的targetName属性值并没有被验证。任何的输入错误仅在client bean实际实例化时才会被发现(可能伴随着致命的错误)。
  • 如果client bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。

此外,如果被引用的bean在同一XML文件内,且bean名字就是bean id,那么可以使用local属性,此属性允许XML解析器在解析XML文件时来对引用的bean进行验证。

<property name="targetName">
   <!-- a bean with an id of 'theTargetBean' must exist, else an XML exception will be thrown -->
   <idref local="theTargetBean"/>
</property>

上面的例子与在ProxyFactoryBean bean定义中使用元素指定AOP interceptor的相同之处在于:如果使用元素指定拦截器名字,可以避免因一时疏忽导致的拦截器ID拼写错误。

在spring中idref是用来将容器内其他bean的id传给和

  • 记住是bean的id名称不是bean对象实例
  • 同时使用idref容器在部署的时候还会验证这个名称的bean是否真实存在
  • idref元素的功能与类似,就是idref多了验证的功能,减少配置的书写错误机率。
  • 除了,如果被引用的bean在同一个xml文件中,且bean的名字就是bean的id,除了可以使用,此属性允许xml解析器在解析XML的时候对引用的bean进行验证。
idref和ref的作用的完全不同的
  • ref是对bean的对象实例的引用,ref同样有两种方式和,
    • bean属性的值可以同目标bean的id属性相同
    • 也可以同目标bean的name属性中任何一个值相同。
  • 用local属性指定目标bean可以利用XML解析器的能力在同一个文件中验证XML id引用。
引用其它的bean(协作者)
  • <ref/>标记指定bean属性的目标bean
    • XML 'bean’元素的值既可以是指定bean的id值也可以是其name值。
  • local属性指定目标bean
    • 可以利用XML解析器来验证所引用的bean是否存在同一文件中
    • local属性值必须是目标bean的id属性值。
  • 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>
集合

通过<list/><set/><map/><props/>元素可以定义和设置与Java Collection类型对应List、Set、Map及Properties的值。

<bean id="moreComplexObject" class="example.ComplexObject">
  <!-- results in a setAdminEmails(java.util.Properties) call -->
  <property name="adminEmails">
    <props>
        <prop key="administrator">administrator@somecompany.org</prop>
        <prop key="support">support@somecompany.org</prop>
        <prop key="development">development@somecompany.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>yup an entry</value>
            </key>
            <value>just some string</value>
        </entry>
        <entry>
            <key>
                <value>yup 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>

注意:map的key或value值,或set的value值不能是以下元素:

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

集合合并

定义parent-style和child-style的<list/>、<map/>、<set/><props/>元素,子集合的值从其父集合继承和覆盖而来;也就是说,父子集合元素合并后的值就是子集合中的最终结果,而且子集合中的元素值将覆盖父集全中对应的值。
关于合并的这部分利用了parent-child bean机制。此内容将在后面介绍,不熟悉父子bean的读者可参见第“bean定义的继承”

<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@somecompany.com</prop>
            <prop key="support">support@somecompany.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@somecompany.com</prop>
            <prop key="support">support@somecompany.co.uk</prop>
        </props>
    </property>
</bean>
<beans>

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

结果为:

administrator=administrator@somecompany.com
sales=sales@somecompany.com
support=support@somecompany.co.uk
Nulls

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

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

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

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

上述的配置等同于Java代码:exampleBean.setEmail(null)。

XML-based configuration metadata shortcuts

针对常见的value值或bean的引用,Spring提供了简化格式用于替代和元素。、及元素都支持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"/>

通常情况下,当手工编写配置文件时,你可能会偏向于使用简写形式(Spring的开发团队就是这么做的)。

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

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

等同于:

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

注意,尽管存在等同于 元素的简写形式,但并没有的简写形式,为了对当前xml中bean的引用,你只能使用完整的形式。

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

等同于: 再次强调,只有元素的简写形式,没有的简写形式。
组合属性名称

只要其他属性值不为null,组合或嵌套属性名是完全合法的。

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

使用depends-on

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

ApplicationContext实现的默认行为就是在启动时将所有singleton bean提前进行实例化。提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean。通常情况下这是件好事,因为这样在配置中的任何错误就会即刻被发现(否则的话可能要花几个小时甚至几天)。

一个延迟初始化bean将告诉IoC 容器是在启动时还是在第一次被用到时实例化。
延迟初始化将通过元素中的lazy-init属性来进行控制。例如:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true">
    <!-- various properties here... -->
</bean>
  <bean name="not.lazy" class="com.foo.AnotherBean">
    <!-- various properties here... -->
</bean>

当ApplicationContext实现加载上述配置时,设置为lazy的bean将不会在ApplicationContext启动时提前被实例化,而not.lazy却会被提前实例化。

需要说明的是,如果一个bean被设置为延迟初始化,而另一个非延迟初始化的singleton bean依赖于它,那么当ApplicationContext提前实例化singleton bean时,它必须也确保所有上述singleton 依赖bean也被预先初始化,当然也包括设置为延迟实例化的bean。因此,如果Ioc容器在启动的时候创建了那些设置为延迟实例化的bean的实例,你也不要觉得奇怪,因为那些延迟初始化的bean可能在配置的某个地方被注入到了一个非延迟初始化singleton bean里面。

在容器层次中通过在元素上使用’default-lazy-init’属性来控制延迟初始化也是可能的。如下面的配置:

<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated... -->
</beans>

注意:
default-lazy-init参数是配置在beans标签中,而lazy-init参数配置在相应需要延时加载的bean标签中,default-lazy-init参数针对所有的bean配置,而lazy-init参数针对需要的延时加载的bean配置,所以lazy-init比default-lazy-init的优先级更高,

自动装配(autowire)

Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系。因此,如果可能的话,可以自动让Spring通过检查BeanFactory中的内容,来替我们指定bean的协作者(其他被依赖的bean)。

autowire一共有五种类型,可以在元素中使用autowire属性指定:

模式zzzzzzzzzzzzzzzzzzzzzzzzzz说明
no不使用自动装配。必须通过ref元素指定依赖,这是默认设置。由于显式指定协作者可以使配置更灵活、更清晰,因此对于较大的部署配置,推荐采用该设置。而且在某种程度上,它也是系统架构的一种文档形式。
byName根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。例如,在bean定义中将autowire设置为by name,而该bean包含master属性(同时提供setMaster(…)方法),Spring就会查找名为master的bean定义,并用它来装配给master属性。
byType如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,那么将会抛出异常,并指出不能使用byType方式进行自动装配。若没有找到相匹配的bean,则什么事都不发生,属性也不会被设置。如果你不希望这样,那么可以通过设置dependency-check="objects"让Spring抛出异常。
constructor与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
autodetect通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。
<bean id="userService" class="com.tamakiakoo.service.impl.UserServiceImpl" autowire="byName"></bean>

<bean id="userDao" class="com.tamakiakoo.service.dao.impl.UserDaoImpl" />
	

自动装配userdao的实现类

public class UserServiceImpl implements IUserService {

	private IUserDao userDao;
	
	@Override
	public void add() {
		System.out.println("UserServiceImpl.add()");
	}

	public IUserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(IUserDao userDao) {
		this.userDao = userDao;
	}
}

  • 如果直接使用property和constructor-arg注入依赖的话,那么将总是覆盖自动装配。
  • 不支持简单类型的自动装配

理解自动装配的优缺点是很重要的。其中优点包括:

  • 自动装配能显著减少配置的数量。不过,采用bean模板(见这里)也可以达到同样的目的。
  • 自动装配可以使配置与java代码同步更新。
    • 例如,如果你需要给一个java类增加一个依赖,那么该依赖将被自动实现而不需要修改配置。因此强烈推荐在开发过程中采用自动装配,而在系统趋于稳定的时候改为显式装配的方式。

自动装配的一些缺点:

  • 尽管自动装配比显式装配更神奇,但是,正如上面所提到的,Spring会尽量避免在装配不明确的时候进行猜测,因为装配不明确可能出现难以预料的结果,而且Spring所管理的对象之间的关联关系也不再能清晰的进行文档化。
  • 对于那些根据Spring配置文件生成文档的工具来说,自动装配将会使这些工具没法生成依赖信息。
  • 如果采用by type方式自动装配,那么容器中类型与自动装配bean的属性或者构造函数参数类型一致的bean只能有一个,如果配置可能存在多个这样的bean,那么就要考虑采用显式装配了。
设置Bean使自动装配失效
  • 你也可以针对单个bean设置其是否为被自动装配对象。
  • 元素的 autowire-candidate属性可被设为false,这样容器在查找自动装配对象时将不考虑该bean。

依赖检查

当需要确保bean的所有属性值(或者属性类型)被正确设置的时候,那么这个功能会非常有用。当然,在很多情况下,bean类会有一些具有默认值的属性,或者有些属性并不会在所有场景下使用,因此这项功能会存在一定的局限性。就像自动装配一样,依赖检查也可以针对每一个bean进行设置。依赖检查默认为not, 它有几种不同的使用模式,在xml配置文件中,可以在bean定义中为dependency-check属性使用以下几种值:

模式说明
none没有依赖检查,如果bean的属性没有值的话可以不用设置。
simple对于原始类型及集合(除协作者外的一切东西)执行依赖检
object仅对协作者执行依赖检查
all对协作者,原始类型及集合执行依赖检查

方法注入

在大部分情况下,容器中的bean都是singleton类型的。如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例。

上述问题的一个解决办法就是放弃控制反转。通过实现BeanFactoryAware接口(见这里)让bean A能够感知bean 容器,并且在需要的时候通过使用getBean(“B”)方式(见这里)向容器请求一个新的bean B实例。看下下面这个例子,其中故意使用了这种方法:

// a class that uses a stateful Command-style class to perform some processing使用有状态命令样式类执行某些处理的类
package fiona.apple;

// lots of Spring-API imports大量Spring-API导入
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

public class CommandManager implements BeanFactoryAware {

   private BeanFactory beanFactory;

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

   // the Command returned here could be an implementation that executes asynchronously, or whatever这里返回的命令可以是异步执行的实现,或者其他
   protected Command createCommand() {
      return (Command) this.beanFactory.getBean("command"); // notice the Spring API dependency注意Spring API依赖项
   }

   public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      this.beanFactory = beanFactory;
   }
}

上面的例子显然不是最好的,因为业务代码和Spring Framework产生了耦合。方法注入,作为Spring IoC容器的一种高级特性,可以以一种干净的方法来处理这种情况。

Lookup方法注入
  • Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例
  • Lookup方法注入适用于原型bean(尽管它也适用于singleton bean,但在那种情况下直接注入一个实例就够了)。
  • ookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码功能,通过动态创建Lookup方法bean的子类而达到复写Lookup方法的目的。

如果你看下上个代码段中的代码(CommandManager类),Spring容器动态覆盖了createCommand()方法的实现。你的CommandManager类不会有一点对Spring的依赖,在下面这个例子中也是一样的:

package fiona.apple;

// no more Spring imports! 

public class CommandManager {

   public Object process(Object command) {
      // 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();
   }

    // mmm, but where is the implementation of this method?嗯,但是这个方法在哪里实现呢?
   protected abstract CommandHelper createHelper();

}

在包含被注入方法的客户类中(此处是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的bean在需要一个新的command bean实例时,会调用createCommand方法。重要的一点是,必须将command部署为原型。当然也可以指定为singleton,如果是这样的话,那么每次将返回相同的command bean实例!

Lookup方法注入既可以结合构造器注入,也可以与setter注入相结合。

请注意,为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里。 另外,Spring容器要子类化的类不能是final的,要覆盖的方法也不能是final的。 同样的,要测试一个包含抽象方法的类也稍微有些不同,你需要自己编写它的子类提供该抽象方法的桩实现。 最后,作为方法注入目标的bean不能是序列化的(serialized)。

提示

  • 有兴趣的读者也许已经发现ServiceLocatorFactoryBean (在org.springframework.beans.factory.config包里)的用法和ObjectFactoryCreatingFactoryBean的有些相似,
  • 不同的是它允许你指定自己的lookup接口,不一定非要用Spring的lookup接口,比如ObjectFactory。
  • 要详细了解这种方法请参考ServiceLocatorFactoryBean的Javadocs(它的确减少了对Spring的耦合)。
自定义方法的替代方案

比起Lookup 方法注入来,还有一种很少用到的方法注入形式,该注入能使用bean的另一个方法实现去替换自定义的方法。除非你真的需要该功能,否则可以略过本节。

当使用基于XML配置元数据文件时,可以在bean定义中使用replaced-method元素来达到用另一个方法来取代已有方法的目的。考虑下面的类,我们将覆盖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
    implementation in MyValueCalculator */
public class ReplacementComputeValue implements MethodReplacer {

    @Override
    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 ...;
}

这个是接口需要实现的方法

@Override
	public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable {
		// TODO Auto-generated method stub
		return null;
	}

下面的bean定义中指定了将要复写的方法以及执行替换处理的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"/>

在元素内可包含一个或多个元素,这些元素用来标明被复写的方法签名。只有被复写(override)的方法存在重载(overload)的情况(同名的多个方法变体)才会使用方法签名。为了方便,参数的类型字符串可以采用全限定类名的简写。例如,下面的字符串都表示参数类型为java.lang.String。

java.lang.String
    String
    Str

参数的个数通常足够用来区别每个可能的选择,这个捷径能减少很多键盘输入的工作,它允许你只输入最短的匹配参数类型的字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值