ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringFramework-Core-1.4 依赖

2021-09-11 13:34:29  阅读:143  来源: 互联网

标签:1.4 Core 依赖 容器 装配 Spring SpringFramework bean 注入


1.4 依赖

一个应用中往往不止存在一个bean,多个bean直接相互依赖和引用才构成了整个应用


1.4.1 依赖注入

首先由对象定义它们的依赖,只通过构造器参数()、工厂方法参数、在对象被构建(通过无参构造或者工厂方法)后设置对象实例的properties;然后容器会在 bean 被创建后注入这些依赖。这个过程就是一个典型的“控制反转”,从 bean 自己控制或者定位它们的依赖变成了由容器根据配置的元数据注入依赖

使用依赖注入原则的代码更清晰,并且在向对象提供依赖时解耦更有效。对象不查找它的依赖项,也不知道依赖项的位置或类。因此,类变得更容易测试,特别是当依赖关系是在接口或抽象基类上时,which allow for stub or mock implementations to be used in unit tests.


基于构造器的依赖注入

基于构造器的依赖注入是通过容器调用有参数构造来实现的,每个参数表示一个依赖项。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final 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 ThingOne {

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

如果ThingTwothingThree之间没有任何继承实现关系,那么下面的bean配置就能完成依赖注入,不需要指定下标、名称……

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

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

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

构造器参数类型匹配

考虑这样一个类:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

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

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

当在使用构造器参数创建bean,并且传入的值为"true"的时候,Spring 可能就无法判断这个值的 type,与之对应的可能是boolean类型的,也可能是String类型的。这个时候可以指定要注入的值的类型,以明确注入行为。

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

构造器参数下标匹配

这里的下标对应被注入类有参构造参数列表的下标,从 0 开始

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

为了保证参数名匹配能够迁移使用,可以启用debug flag或者使用JDK自带的@ConstructorProperties注解来标注构造器的参数:

package examples;

public class ExampleBean {

    // Fields omitted

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

基于 Setter 的依赖注入

基于setter的DI是由容器在调用无参数构造函数或无参数静态工厂方法实例化bean后调用bean上的setter方法来实现的。

ApplicationContext支持基于构造器的 bean 依赖注入,也支持基于 setter 的注入,还支持通过构造器注入一部分后使用 setter 注入。


基于构造器还是 Setter?

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.


依赖解析的过程

容器执行 bean 的依赖解析的过程如下:

  • ApplicationContext被带有所有描述所有 bean 的元数据配置创建。配置元数据可以通过XML、Java、注解实现
  • 对于每一个 bean,它的依赖通过 properties、构造器参数或者静态工厂参数指定。当 bean 被创建的时候,这些依赖会被提供
  • 每一个属性或者构造器参数都是一个明确定义的值或者是对容器中其它 bean 的引用
  • 每个作为值的属性或构造函数参数将从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,比如' int '、' long '、' string '、' boolean '等等。

Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,bean属性的 properties 不会被设置。在创建容器的时候,bean 都是单例作用域的,并且是预实例化(默认值)。作用域在 Bean 中通过lazy-init属性定义。bean 只在被需要的时候创建

一个 bean 的创建往往会导致一堆 bean 被创建,因为 bean 之间会存在相互依赖,直到依赖具体值的 bean 被创建,上层的 bean 才会成功创建。

依赖间的解析不匹配往往会在导致不匹配的 bean 被需要和创建时才会出现,而不是在容器被创建的时候。


循环依赖问题

如果主要使用构造器注入,可能会出现不可解析的循环依赖的场景。主要是因为使用有参构造时,构造器指定的参数必须存在。

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

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

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


Spring尽可能晚地在实际创建bean时设置属性和解析依赖项。这意味着,如果在创建对象或其中一个依赖项时出现问题,则正确加载的Spring容器稍后可以在请求对象时生成异常——例如,bean会因为缺少或无效的属性而抛出异常。这可能会延迟一些配置问题的可见性,这就是为什么ApplicationContext实现默认预实例化单例bean。当然,你也可以手动配置 bean 为 懒加载,但这样的话,诸如循坏依赖这样的问题可能就无法被即时发现。

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


1.4.2 依赖和配置细节

基本类型和 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>

在填写好 value 里的值后,Spring 的转换服务会自动把字符转换成对应的类型。

还可以使用 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 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>

还可以对java.util.Properties进行注入:

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

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

idref 标签

idref标签是用来注入值而非引用的,注入的值必须是已经存在的 bean 的 id,因此使用这个标签能达到 error-proof 的效果:

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

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

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

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

这两种配置完全等价。第一种形式比第二种形式更可取,因为使用 idref 标记可以让容器在部署时验证所引用的已命名bean是否实际存在。在第二个变体中,没有对传递给 bean 的 targetName 属性的值执行验证。只有在实际实例化 bean 时才会发现输入错误(很可能导致致命的结果)。如果 bean 是一个原型 bean,则可能只有在容器部署很久之后才会发现这种输入错误和由此产生的异常(相对于单例 bean 在容器初始化时就能发现错误而言)。


引用其他 bean

ref属性是 property或者<constructor-arg/>标签的最后一个属性,我们可以通过它引用容器中的其它 bean。被引用的 bean 会在被需要时完成初始化,如果被引用的 bean 是单例的,那么它可能在容器被创建时就完成了实例化。所有的引用在本质上就是对其它对象的引用,Scoping and validation depend on whether you specify the ID or name of the other object through the bean or parent attribute.

通过 bean 标签的 ref 属性指定目标 bean 是最通用的形式,它允许在相同容器或父容器中创建对任何bean的引用,不管它们是否在相同的 XML 配置文件中。bean 属性的值可以与目标 bean 的 id 属性相同,也可以与目标 bean 的 name 属性中的一个值相同。

<bean id="a" class="com.ermao.chapter1_4.A">
    <property name="b" ref="b"/>
    <property name="value" value="a"/>
</bean>

<bean id="b" class="com.ermao.chapter1_4.B" >
    <property name="a">
        <ref bean="a"/>
    </property>
    <property name="value" value="b"/>
</bean>

TODO

Specifying the target bean through the parent attribute creates a reference to a bean that is in a parent container of the current container. The value of the parent attribute may be the same as either the id attribute of the target bean or one of the values in the name attribute of the target bean. The target bean must be in a parent container of the current one. You should use this bean reference variant mainly when you have a hierarchy of containers and you want to wrap an existing bean in a parent container with a proxy that has the same name as the parent bean. The following pair of listings shows how to use the parent attribute:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as 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 here -->
</bean>

内部 Bean

就是一个简单的嵌套:

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

内部bean定义不需要定义 id 或 name。即使指定了,容器也不使用它作为标识符。容器在创建时也会忽略 scope 标志,因为内部 bean 总是匿名的,并且总是与外部bean一起创建的。不可能独立地访问内部bean,也不可能将它们注入到外部 bean 之外的协作 bean 中。

TODO

As a corner case, it is possible to receive destruction callbacks from a custom scope — for example, for a request-scoped inner bean contained within a singleton bean. The creation of the inner bean instance is tied to its containing bean, but destruction callbacks let it participate in the request scope’s lifecycle. This is not a common scenario. Inner beans typically simply share their containing bean’s scope.


集合

The <list/>, <set/>, <map/>, and <props/> elements set the properties and arguments of the Java Collection types List, Set, Map, and Properties, respectively. The following example shows how to use them:

<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 中的键值对可以由以下几种标签指定:

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

集合融入

集合的值也是集合,并且子集合的值是融合父集合和子集合之后的值,对于相同的值,子集合中的值会覆盖父集合中的值

<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"> <!-- 这个地方不指定 bean 就无法创建子 bean -->
        <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>
    
    
    
    <bean id="parent" abstract="true" class="com.ermao.chapter1_4.collection_merge.AdminEmail">
        <property name="adminEmail">
            <props>
                <prop key="name">Ermao</prop>
                <prop key="age">19</prop>
            </props>
        </property>
    </bean>

    <bean id="son" parent="parent" class="com.ermao.chapter1_4.collection_merge.AdminEmailChild">
        <property name="adminEmail">
            <props merge="true">
                <prop key="hobby">coding</prop>
                <prop key="age">20</prop>
            </props>
        </property>
    </bean>	
<beans>

输出结果如下:

hobby==>coding
name==>Ermao
age==>20

可以看到子集合的值重写了父集合的值,并且纳入了那些父集合有而自己没有的部分。前提是,两个 bean 对应的类之间必须要有继承关系


空串和 null

要注入空串和 null:

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

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

p 和 c 命名空间

应该尽量避免使用,虽然可以简化


注入属性的属性

You can use compound or nested property names when you set bean properties, as long as all components of the path except the final property name are not null. Consider the following bean definition:

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

The something bean has a fred property, which has a bob property, which has a sammy property, and that final sammy property is being set to a value of 123. In order for this to work, the fred property of something and the bob property of fred must not be null after the bean is constructed. Otherwise, a NullPointerException is thrown.


1.4.3. 使用depends-on

有些时候,我们对依赖的使用不是那么直接,比如在使用 JDBC 的驱动时,我们只是通过驱动类注册驱动,不需要显式的依赖驱动类。但驱动的注册是在静态代码块中完成的,所以我们需要初始化它,这时候,只需要使用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="x.y.jdbc.JdbcAccountDao" />

1.4.4. 懒初始化 Bean

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

要注意的是,如果懒加载 bean 被其他单例 bean 引用,那么仍然会在容器创建时被初始化。

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

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

1.4.5. Autowiring Collaborators

自动装配的优点:

  • 自动装配可以显著减少指定属性或构造函数参数的需要
  • 自动装配可以随着对象的发展更新配置。例如,如果您需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。

当使用基于xml的配置元数据时(请参阅依赖注入),可以使用<bean/>的自动装配属性为 bean 定义指定自动装配模式。

自动装配功能有四种模式:

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

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

自动装配的局限和弊端

如果整个项目都使用自动装配,那么效果将会很好,但是如果只在一两个 bean 上使用自动装配,结果可能会不太乐观。

具体的局促有以下几点:

  • 显式装配会覆盖自动装配。并且自动装配无法装配基本类型、字符串、类
  • Bean 之间的依赖关系没有显式装配那样明确
  • 装配信息可能不能在 Spring 容器的文档中反映出来
  • 容器内的多个 bean 定义可以与要自动连接的 setter 方法或构造函数参数指定的类型匹配。对于数组、集合或Map实例,这并不一定是个问题。然而,对于期望单个值的依赖项,这种模糊性是不能任意解决的。如果没有唯一的bean定义可用,则抛出异常。

使用自动装配的一些选择

  • 放弃使用自动装配
  • 将 bean 的autowire-candidate属性设置为 false 以防止它被自动装配
  • 将单例设置成自动装配候选对象,通过将 primary 设置成 true 来实现
  • 通过注解来实现更加细粒度的自动装配

Excluding a Bean from Autowiring

通过将autowire-candidate属性设置为 false 来防止 bean 被自动装配,设置好之后,就连使用@AutoWired注解的 bean 定义都无法被装配。

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

还可以在顶级的<beans>标签中设置属性 default-autowire-candidates来指定这个配置文件中的候选装配 bean ,支持通配符,多个候选对象之间通过逗号分割,但是autowire-candidate属性的作用会高过上述属性的总用。

这些技术对于那些永远不想通过自动装配将其注入到其他 bean 中的 bean 非常有用。这并不意味着被排除的 bean 本身不能通过使用自动装配来配置。只是这些 bean 本身不是自动装配其他 bean 的候选对象。


1.4.6. Method Injection(方法注入)

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

一个解决方案是放弃一些控制反转。可以通过实现 ApplicationContextAware 接口,以及在 bean A 每次需要 bean B 实例时对容器进行 getBean(“B”) 调用,从而使 bean A 意识到容器。

// 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 Injection(查询方法注入)

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

  • For this dynamic subclassing to work, the class that the Spring bean container subclasses cannot be final, and the method to be overridden cannot be final, either.
  • Unit-testing a class that has an abstract method requires you to subclass the class yourself and to supply a stub implementation of the abstract method.
  • Concrete methods are also necessary for component scanning, which requires concrete classes to pick up.
  • A further key limitation is that lookup methods do not work with factory methods and in particular not with @Bean methods in configuration classes, since, in that case, the container is not in charge of creating the instance and therefore cannot create a runtime-generated subclass on the fly.

对于前面代码片段中的 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);

如果方法是抽象的,则动态生成的子类实现该方法。否则,动态生成的子类将重写在原始类中定义的具体方法。

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" 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="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,而不需要提供返回 bean 的 id 或者 name。

public abstract class CommandManager {

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

    @Lookup
    protected abstract MyCommand createCommand();
}

注意,通常应该使用 concrete stub implementation 声明这种带注释的查找方法,以便它们与Spring的组件扫描规则(默认情况下抽象类会被忽略)兼容。此限制不适用于显式注册或显式导入的 bean 类。


Arbitrary Method Replacement

方法注入的另一种比不上查找方法注入有用的形式是,用另一种方法实现替换托管 bean 中的任意方法。在真正需要此功能之前,您可以安全地跳过本节的其余部分。

对于基于 xml 的配置元数据,可以使用 replacement -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">
    <!-- 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"/>

标签:1.4,Core,依赖,容器,装配,Spring,SpringFramework,bean,注入
来源: https://www.cnblogs.com/locustree/p/15253527.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有