• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

一起来读官方文档—–SpringIOC(04)

开发技术 开发技术 2周前 (09-13) 17次浏览
1.4.2。依赖性和详细配置

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

直值(原语,字符串等)
在value所述的属性元素指定属性或构造器参数的人类可读的字符串表示。Spring的 转换服务用于将这些值从转换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="misterkaoli"/>
</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 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定义时支持自动属性完成。

您还可以配置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容器将元素中的文本转换为java.util。属性实例,通过使用JavaBeans的PropertyEditor机制。这是一个很好的快捷方式,也是Spring团队偏爱使用嵌套的元素而不是value属性样式的几个地方之一。

idref元素

所述idref元件是一个防错方法,主要通过将该容器中的另一个bean的id(将id作为一个字符串值传递-而不是引用)传递给标签。

以下示例显示了如何使用它:

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

<bean id="theClientBean" class="...">
    <property name="targetName">
        //此处将 theTargetBean 作为字符串传递给 theClientBean的targetName属性
        //而不是将theTargetName这个bean的实例传递给targetName属性
        <idref bean="theTargetBean"/>
    </property>
</bean>

前面的bean定义代码段(在运行时)与以下代码段完全等效:
idref 等价的是 value标签 而不是 ref标签

<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>
元素 的local属性在idref4.0 Bean XSD中不再受支持,因为它不再提供常规bean引用上的值。
升级到4.0模式时,将现有idref local引用更改为idref bean。

idref用法 可以校验传入的作为bean的id 会被用来校验当前id的bean存不存在

<idref></idref>元素常用的位置(至少在spring2.0之前的版本中)是在ProxyFactoryBean bean定义中的AOP拦截器配置中。
在指定拦截器名称时使用<idref></idref>元素可以防止拼写错误。
ref 对其他Bean的引用

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

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

//someBean 可以是bean的id  也可以是bean的name
<ref bean="someBean"/>

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

集合

、元素分别设置Java集合类型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@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="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </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
集合合并

Spring容器还支持合并集合。应用开发者可以定义一个父元素

或,让子元素

或继承和覆盖父元素集合的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。

关于合并的这一节讨论父-子bean机制。不熟悉父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定义的adminEmails属性的元素上使用了merge=true属性。当容器解析并实例化子bean时,生成的实例具有一个adminEmails属性集合,该集合包含将子bean的adminEmails集合与父bean的adminEmails集合合并的结果。下面的清单显示了结果:

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

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

这种合并行为类似地应用于

集合类型。在元素的特定情况下,将维护与列表集合类型(即值的有序集合的概念)相关联的语义。父元素的值位于所有子元素列表的值之前。对于Map、Set和Properties集合类型,不存在排序。因此,对于位于容器内部使用的关联映射、集合和属性实现类型下的集合类型,排序语义不起作用。

集合合并的局限性

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

强类型集合

随着Java 5中泛型类型的引入,您可以使用强类型集合。也就是说,可以声明一个Collection类型,使其仅包含(例如)String元素。如果使用Spring将强类型依赖注入Collection到Bean中,则可以利用Spring的类型转换支持,以便在将强类型Collection 实例的元素添加到之前将其转换为适当的类型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属性时,
可以通过反射获得something有关强类型的元素类型的泛型信息Map<String,Float>。
因此,Spring的类型转换基础结构将各种值元素识别为type Float,
并将字符串值(9.99, 2.75和 3.99)转换为实际Float类型。
空字符串值和空字符串值

Spring将属性之类的空参数视为空字符串。以下基于xml的配置元数据片段将email属性设置为空字符串值("")。

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

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

exampleBean.setEmail("");

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

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

前面的配置等效于下面的Java代码:

exampleBean.setEmail(null);
带有p-名称空间的XML快捷方式

p-名称空间允许您使用bean元素的属性(而不是嵌套的元素)来描述与之合作的bean的属性值,或者两者都使用。

Spring支持带有名称空间的可扩展配置格式,名称空间基于XML模式定义。本章中讨论的bean配置格式是在XML模式文档中定义的。但是,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="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

该示例显示了email在bean定义中调用的p-namespace中的属性。这告诉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
        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-名称空间的属性值,而且还使用一种特殊格式来声明属性引用。第一个bean定义使用来创建一个从bean john到bean jane的引用,第二个bean定义使用p:spouse-ref="jane"作为一个属性来完成完全相同的工作。在本例中,spouse是属性名,而-ref部分表明这不是一个直接的值,而是对另一个bean的引用。

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

	The p-namespace is not as flexible as the standard XML format. 
	For example, the format for declaring property references clashes 
	with properties that end in Ref,whereas the standard XML format does not.
	We recommend that you choose your approach carefully 
	and communicate this to your team members to avoid producing XML documents
	that use all three approaches at the same time.
	
	这段没看懂,这里测试了以Ref结尾的属性也是可以用 p:xxxRef-ref
具有c-namespace的XML快捷方式

与使用p-namespace的XML快捷方式类似,Spring 3.1中引入的c-namespace允许使用内联属性配置构造函数参数,而不是嵌套构造函数参数元素。说白了就是p-namespace替换property,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/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="something@somewhere.com"/>
    </bean>

    <!-- 带有参数名称的c-名称空间声明 -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

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

<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>
复合属性名称 一个bean中嵌套另外一个bean 并需要给内部的bean赋值

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

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


所述something bean具有fred属性,该属性具有bob属性,
bob属性具有sammy 特性,并且最终sammy属性被设置为值123。  
something bean的fred属性和fred的bob属性在构造bean之后一定不能为null。  
否则,将会引发NullPointerException。
1.4.3。使用depends-on

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

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

要表达对多个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" />
depends-on属既可以指定初始化时间依赖项,
也可以指定对应的销毁时间依赖项(仅在单例bean中)。被依赖的bean会晚于依赖bean之后销毁

<bean id="serviceOneRef" name="serviceOneName" class="org.springframework.example.service.ServiceOne"
		  destroy-method="destroyed"/>
    public void destroyed(){
		System.out.println("ServiceOne destroy");
	}
	
<bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo"
		  p:name="asdfasdf"  depends-on="serviceOneRef"
		  destroy-method="destroyed" />
    public void destroyed(){
		System.out.println("serviceTwo destroy");
	}
		  
console:
    serviceTwo destroy
    ServiceOne destroy
1.4.4。懒加载bean

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

在XML中,这种行为是由元素的lazy-init属性控制的,如下面的例子所示:

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

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

然而,当懒加载的bean是非懒加载的单例bean的依赖项时,ApplicationContext在启动时则会创建懒加载的bean,因为它必须满足单例的依赖项。

您还可以通过使用元素的default-lazy-init批量设置懒加载bean

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
1.4.5。自动装配

Spring容器可以自动装配协作bean之间的关系。
自动装配具有以下优点:

  • 自动装配可以大大减少指定属性或构造函数参数的需要。
  • 随着对象的发展,自动装配可以更新配置。例如,如果您需要向类中添加一个依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,而不必担心当代码库变得更稳定时切换到显式接线的选择。

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

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

使用byType或constructor自动装配模式,您可以自动注入arrays和collections类型。在这种情况下,将提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。
如果接收的map 的key值类型为String,那么你也可以让Spring自动装配Map类型的值,并且 Map实例的key值为相应的bean名称。

	@Autowired
	private List<CusService> serviceLists;

	@Autowired
	private Map<String,CusService> cusServiceMap;
自动接线的局限性和缺点

自动装配在项目中一致使用时工作得最好。如果自动装配没有被普遍使用,那么使用它来连接一个或两个bean定义可能会使部分开发人员感到困惑。

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

  • 属性和构造参数设置中的显式依赖关系总是覆盖自动装配。您不能自动连接简单属性,如primitives(boolean,int,long等)、String和classes(以及此类简单属性的数组)。这种限制被刻意设计的。
  • 自动装配bean不如显式指定bean精确。不过,正如前面的表中所指出的,Spring已经尽可能避免产生意外结果。Spring管理的对象之间的关系不再被明确地记录。
  • 对于能从Spring容器生成文档工具来说生成连接信息是不可能的了。
  • 自动注入依赖项时如果有多个可以匹配的选项,如果注入类型是数组、集合或映射实例,这不是问题。但是,对于期望使用单个值的依赖项,这种模糊性不能任意解决。如果没有可用的唯一bean定义,则抛出异常。

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

  • 放弃自动装配,支持显式布线。
  • 通过将bean定义的autowire-candidate设置为false来避免自动装配。
  • 通过将单个bean定义的元素的primary设置为true,将其指定为主候选bean定义。
  • 使用基于注释的配置实现更细粒度的控制,1.9小节会专门讲解注解使用。
1.放弃自动装配,改成指定bean注入 @Qualifier 或者 xml的ref属性都可以
2.
    	<bean id="serviceOne"  class="org.springframework.example.service.ServiceOne" />
    	<bean id="serviceTwo"  class="org.springframework.example.service.ServiceTwo" />
    两个bean都继承同一个接口CusService,如果有自动装配如下
    	@Autowired
    	private CusService service;
    则启动时候会报错
    如果给serviceOne增加属性autowire-candidate="false" 
    	<bean id="serviceOne"  class="org.springframework.example.service.ServiceOne" autowire-candidate="false" />
    则所有的自动装配CusService的接口都会优先装配serviceTwo
3.情况同2 还可以将serviceTwo 增加primary="true" 
        <bean id="serviceTwo"  class="org.springframework.example.service.ServiceTwo" primary="true" />
从自动装配中排除Bean

在每个bean的基础上,您可以从自动装配中排除一个bean。在Spring的XML格式中,将元素的autowire-candidate设置为false。容器使得特定的bean定义对自动装配基础设施不可用(包括注释风格配置,如@Autowired)。

autowire-candidate属性被设计为只影响基于类型的自动装配。
它不影响按名称的显式引用,即使指定的bean没有标记为自动装配候选,
也会解析显式引用。因此,如果名称匹配,按名称自动装配仍然会注入一个bean。

元素在其default-autowire-candidates属性接收一个patterns 字符串,意思是根据patterns 字符串匹配到的所有合格的beanName的autowire-candidates会被设置为true,不合格的会被设置为false
patterns 字符串接受一个或多个匹配模式,多个patterns 字符串之间可以用逗号隔开。 例如 (Repository,Service,*Dao) 这种组合模式

bean本身的autowire-candidates属性优于的default-autowire-candidates属性生效

这些技术对于那些您永远不希望通过自动装配被注入到其他bean中的bean非常有用。这并不意味着被排除的bean本身不能使用自动装配进行配置。相反,该bean本身仅仅是不作为其他bean的自动连接候选对象。

	    public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate";

        //获取autowire-candidate  这个值默认就是true
        String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
        //判断 是不是定义的当前bean是不是 default
		if (isDefaultValue(autowireCandidate)) {
		    //如果是default 
		    //查找当前bean 所在beans的default-autowire-candidates属性 找到配置的patterns表达式
		    //如果表达式为空 就不处理setAutowireCandidate属性值 这样该属性依旧是true
			String candidatePattern = this.defaults.getAutowireCandidates();
			if (candidatePattern != null) {
			    //表达式不为空 判断当前beanName 是否在表达式范围内
			    //在范围内就setAutowireCandidate设置为true
			    //否则设置为false
				String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
				bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
			}
		}
		else {
		//如果autowireCandidate 不是 default 是true 那就设置为true 
		//是false 那就设置为false
			bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
		}
1.4.6。方法注入

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

一个解决方案是放弃某些控制反转。您可以通过实现接口ApplicationContextAware ,并使每次容器 A都需要容器 B 的调用来请求(通常是新的)bean B实例,从而使bean A知道容器。以下示例显示了此方法:ApplicationContextAware.getBean("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容器的一项高级功能,使您可以干净地处理此用例。

您可以在此博客条目中了解有关方法注入动机的更多信息。

Lookup Method注入

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

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

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

package fiona.apple;
public abstract class CommandManager {

    public Object process(Object commandState) {
        // 获取适当的命令接口的新实例
        Command command = createCommand();
        // 在(希望是全新的)命令实例上设置状态
        command.setState(commandState);
        return command.execute();
    }

    // 实现类在哪呢???
    protected abstract Command createCommand();
}

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

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

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

<!-- 作为原型部署的有状态bean(非单例)-->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

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

标识为commandManager的bean在需要myCommand 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类。

访问范围不同的目标bean的另一种方法是ObjectFactory/ Provider注入点(https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other-injection)。


您可能还会发现ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config包装中)有用。
任意方法替换

方法替换注入的形式是用另一个方法实现替换托管bean中的任意方法的能力。这个十分不常用,您可以跳过本节的其余部分,等到您真正需要此功能再来看。

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

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

实现该org.springframework.beans.factory.support.MethodReplacer 接口的类提供了新的方法定义,如以下示例所示:

/**
 *用于覆盖现有的computeValue(String input)
 *实现在MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        //获取输入值,使用它,并返回计算结果
        String input = (String) args[0];
        ...
        return ...;
    }
}

用于部署原始类并指定方法覆盖的Bean定义类似于以下示例:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

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

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

java.lang.String
String
Str

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


喜欢 (0)