您的位置:首页 > 编程语言 > Java开发

Spring 4.2 方法注入解决单例Bean的原型Bean依赖问题

2015-09-23 00:00 776 查看
摘要: Spring 4.2 方法注入 单例Bean的原型Bean依赖

当你在单例Bean中使用原型Bean依赖时,注意,依赖在实例化时解析。因此,如果你依赖注入一个原型Bean到单例Bean中,新的原型Bean被实例化然后依赖注入到单例Bean中。提供给单例Bean的原型实例是唯一的。

然而,假设你想单例Bean在运行时多次获取一个新的原型Bean的实例。你不能依赖注入一个原型Bean到你的单例Bean中,因为注入只发生一次,当Spring容器实例化单例Bean时解析并注入它的依赖。如果你在运行时多次需要一个新的原型Bean,可以使用方法注入。

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

一个解决方式是丢弃一些控制反转的灵活性。你能通过实现ApplicationContextAware接口使Bean A感知容器,Bean A每次需要的时候通过调用getBean(“B”)向容器询问新的Bean B实例。

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

Command command = createCommand();

command.setState(commandState);

return command.execute();

}

protected Command createCommand() {

return this.applicationContext.getBean("command", Command.class);

}

public void setApplicationContext(

ApplicationContext applicationContext) throws BeansException {

this.applicationContext = applicationContext;

}

}

上面的方式是不可取的,因为业务代码感知和耦合Spring框架。此时可以使用Spring IoC容器的高级特性方法注入。

1 查找方法注入

查找方法注入是容器覆盖容器管理Bean的方法的能力,返回容器中其它命名Bean的查找结果。查找通常涉及前面情景中描述的原型Bean。Spring框架通过CGLIB类库使用二进制动态生成一个子类覆盖方法实现该方法注入。

为了让动态子类能正常工作,Spring Bean容器将继承的类不能是final的,并且要覆盖的方法不能是final的。

单元测试类有一个abstract方法需要你继承自己的类并提供abstract方法实现。

具体方法也需要组件扫描必须的具体类获取。

进一步约束,查询方法不能是工厂方法尤其不能使用配置类中的@Bean 方法,因为在这种情况下容器不能改变创建的实例因此不能创建运行时产生的子类。

最后,对象的方法注入Bean不能被序列化。

看看之前的CommandManager类,Spring容器将自动覆盖createCommand()方法的实现。你的CommandManager类将没有任意Spring依赖。

package fiona.apple;

// 没有更多的Spring imports!

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 id="command" class="fiona.apple.AsyncCommand" scope="prototype">

</bean>

<bean id="commandManager" class="fiona.apple.CommandManager">

<lookup-method name="createCommand" bean="command"/>

</bean>

无论何时需要一个新的command Bean时,Bean都认定CommandManager调用它自己的createCommand()方法。你务必注意部署的command bean是原型,如果实际需要。如果它部署为单例,每次返回的command bean是相同的实例。

感兴趣的读者也可以看看(在org.springframework.beans.factory.config包中的)ServiceLocatorFactoryBean也使用。在ServiceLocatorFactoryBean中使用的方法类似于其它工具类,ObjectFactoryCreatingFactoryBean,但是它允许你指定与Spring特定的查询接口不同的查询接口。

2 任意方法替换

一种与查询方法注入不那么有用的方法注入的形式是能够在管理bean中使用其它方法实现替换任意方法。

使用基于XML的配置元数据,你能使用replaced-method元素替换已存在的实现方法。思考下面的类,有一个computeValue方法,我们想要覆盖:

public class MyValueCalculator {

public String computeValue(String input) {

// 一些真实代码...

}

// 一些其它方法...

}

一个类实现org.springframework.beans.factory.support.MethodReplacer接口提供新方法定义。

/**

* 用于覆盖已存在的MyValueCalculator.computeValue(String)

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

你能在<replaced-method/>元素中包含一个或多个< arg-type/>元素表示被覆盖方法的方法签名。只有类中存在方法重载和多个变体参数签名才是必须的。为了方便起见,参数的类型字符串可以是完全类型名的子串。例如,以下所有匹配java.lang.String:

java.lang.String

String

Str

因为数量的参数通常是足以区分每一个可能的选择,这个快捷方式可以节省很多输入,允许你输入简称字符串匹配参数类型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息