精品Java收藏

Spring 核心技术(5)

2019-08-01  本文已影响2人  小明同学的学长

接上篇:Spring 核心技术(4)

version 5.1.8.RELEASE

1.4.5 自动装配协作者

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

使用基于 XML 的配置元数据(请参阅依赖注入)时,可以使用 <bean/> 元素的 autowire 属性为 bean 定义指定装配模式。自动装配功能有四种模式。你指定每个 bean 的自动装配,因此可以选择要自动装配哪些。下表描述了四种自动装配模式:

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

使用 byTypeconstructor 自动装配模式,可以装配数组和类型化的集合。在这种情况下,容器中所有与预期类型匹配的自动装配候选者都会被提供以满足依赖性。如果预期的键类型是 String,则可以自动装配强类型 Map 实例。自动装配 Map 实例的值由与预期类型匹配的所有 bean 实例组成, Map 实例的键包含相应的 bean 名称。

自动装配的局限和缺点

当在整个项目中使用一致的自动装配时,自动装配效果最佳。如果一般不使用自动装配,那么开发人员使用它来装配一个或两个 bean 定义可能会让人感到困惑。

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

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

从自动装配中排除 Bean

基于每个 bean,你可以从自动装配中排除 bean。在 Spring 的 XML 格式中,将 <bean/> 元素的 autowire-candidate 属性设置为 false。容器使指定的 bean 定义对自动装配不可用(包括注解配置,例如 @Autowired)。

autowire-candidate 属性旨在仅影响基于类型的自动装配。它不会影响根据名称的显式引用,即使指定的 bean 未标记为自动注解的候选人,也会解析它。因此,如果名称匹配,则按名称自动装配会注入 bean。

你还可以根据 bean 名称的表达式匹配来限制自动装配候选项。顶级的 <beans/> 元素在 default-autowire-candidates 属性中接受一个或多个表达式 。例如,要将自动装配候选状态限制为名称以 Repository 结尾的任何 bean,那么就提供值 *Repository。需要提供多个表达式时,请在列表中定义并使用逗号分隔。bean 定义的 autowire-candidate 属性优先使用显示定义的值 truefalse。对于此类 bean,表达式匹配规则并不适用。

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

1.4.6 方法注入

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

解决方案是放弃一部分控制反转。你可以通过实现 ApplicationContextAware 接口使 bean A 对容器可见,同时通过让容器调用 getBean("B") 在每次需要时请求 bean B 的实例。以下示例展示了此方法:

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

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

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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

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

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

前面提到的方法并不理想,因为业务代码知道并耦合到 Spring Framework。方法注入是Spring IoC容器的一个高级功能,可以更加轻松地处理这种场景。

你可以在此博客中阅读有关方法注入成因的更多信息。

查找方法注入

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

  • 要使这个动态子类工作,Spring bean 容器子类不能设置为 final,要重写的方法也不能设置为 final
  • 对具有 abstract 方法的类进行单元测试需要你自己对类进行子类化并提供该 abstract 方法的 stub 实现。
  • 组件扫描也需要具体的方法,这需要具体的类来获取。
  • 另一个关键限制是查找方法不适用于工厂方法,特别是 @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);

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

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

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

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

访问不同作用域的目标 bean 的另一种方法是 ObjectFactory/Provider 注入点。请参阅使用 Scoped Beans 作为依赖关系

你可能还会发现 ServiceLocatorFactoryBean(在 org.springframework.beans.factory.config 包中)有用。

任意方法替换

与查找方法注入相比,一种不太有用的方法注入形式是能够使用另一个方法实现替换托管 bean 中的任意方法。你可以安全地跳过本节的其余部分,直到确实需要此功能。

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

public class MyValueCalculator {

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

    // some other methods...
}

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

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

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

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

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

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

你可以使用一个或多个 <replaced-method/> 元素的 <arg-type/> 元素来表明被覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有均匹配 java.lang.String

java.lang.String
String
Str

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

上一篇下一篇

猜你喜欢

热点阅读