《Spring Framework实战》15:4.1.4.6.方法注入
欢迎观看《Spring Framework实战》视频教程
-
-
-
- 方法注入
-
-
在大多数应用场景中,容器中的大多数bean都是单例(singletons)的。当单例bean需要与另一个单例bean协作或非单例bean需与另一非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(prototype)bean B,可能是在A的每次方法调用时。容器只创建单例bean A一次,因此只有一次机会设置属性。容器不能在每次需要bean B时为bean A提供bean B的新实例。
一个解决方案是放弃一些控制反转。您可以通过实现ApplicationContextAware接口,并在每次bean A需要时对容器进行getBean(“B”)调用,请求(通常是新的)bean B实例,使bean A知道容器。
以下示例显示了这种方法:
Java
package org.examples;
import java.util.Map;
public class Command {
private Map state;
public Map getState() {
return state;
}
public void setState(Map state) {
this.state = state;
}
public String execute() {
return "Command " + this + " execute, state = " + this.state;
}
}
package org.examples;
import java.util.Map;
/**
* 当bean的生命周期不同时,就会出现问题。
*/
public class CommandManager {
public Command command;
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public String process(Map State) {
// 在(希望是全新的)Command实例上设置状态
this.command.setState(State);
return this.command.execute();
}
}
<bean id="commandManager" class="org.examples.CommandManager" scope="singleton">
<!-- collaborators and configuration for this bean go here -->
<property name="command" ref="command"/>
</bean>
<bean id="command" class="org.examples.Command" scope="prototype">
<!-- collaborators and configuration for this bean go here -->
<property name="state">
<map>
<entry key="state1" value="aaa"/>
<entry key="state2" value="bbb"/>
<entry key="state3" value="ccc"/>
</map>
</property>
</bean>
CommandManager commandManager = (CommandManager) context.getBean(CommandManager.class);
Map map1 = new HashMap();
map1.put("state", "state111");
System.out.println(commandManager.process(map1));
Map map2 = new HashMap();
map2.put("state", "state222");
System.out.println(commandManager.process(map2));
package org.examples;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Map;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
* <p>
* 您可以通过实现ApplicationContextAware接口,
* 并在每次bean A需要时对容器进行getBean(“B”)调用,
* 请求(通常是新的)bean B实例,使bean A知道容器。
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public String 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实例上设置状态
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;
}
}
<bean id="commandManager" class="org.examples.CommandManager" scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="command" class="org.examples.Command" scope="prototype">
<!-- collaborators and configuration for this bean go here -->
<property name="state">
<map>
<entry key="state1" value="aaa"/>
<entry key="state2" value="bbb"/>
<entry key="state3" value="ccc"/>
</map>
</property>
</bean>
前面的内容是不可取的,因为业务代码意识到并耦合到Spring Framework。方法注入是Spring IoC容器的一个稍微高级的功能,可以让你干净利落地处理这个用例。
你可以在这篇博客文章中关于方法注入的动机。
-
-
-
-
- 查找方法注入
-
-
-
查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及一个原型bean,如前一节所述的场景。Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现了这种方法注入。
|
在前面代码片段中的CommandManager类的情况下,Spring容器动态覆盖createCommand()方法的实现。CommandManager类没有任何Spring依赖项,如重新编写的示例所示:
Java
package org.examples;
// 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="org.examples.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="org.examples.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommand bean的新实例时调用自己的createCommand()方法。如果确实需要,您必须小心地将myCommand bean部署为原型。如果它是单例,则每次都会返回相同的myCommand bean实例。
或者,在基于注释的组件模型中,您可以通过@lookup注释声明查找方法,如下例所示:
Java
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根据查找方法的声明返回类型进行解析:
Java
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
请注意,您通常应该使用具体的存根实现声明此类带注释的查找方法,以便它们与Spring的组件扫描规则兼容,默认情况下抽象类会被忽略。此限制不适用于显式注册或显式导入的bean类。
访问不同作用域的目标bean的另一种方法是ObjectFactory/Provider注入点。请参阅作为依赖关系的作用域Bean。 您可能还会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)很有用。 |
-
-
-
-
- 任意方法替换
-
-
-
一种不如查找方法注入有用的方法注入形式是能够用另一种方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您真正需要此功能。
使用基于XML的配置元数据,您可以使用替换的方法元素将已部署bean的现有方法实现替换为另一个方法实现。考虑以下类,它有一个我们想要重写的名为computeValue的方法:
Java
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
一个实现org.springframework.beans.factory.support的类。MethodReplacer接口提供了新的方法定义,如下例所示:
Java
/**
* 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
因为参数的数量通常足以区分每种可能的选择,所以这个快捷方式可以节省大量的输入,只让你输入与参数类型匹配的最短字符串。