方法注入主要是用在Singleton的Object中使用非Singleton的Bean时,通过lookup-method的那个方法来取得非Singleton的Bean。一般用的不多,在用这种定义之前最好想明白你的需求。
1 使用Java代码实现方法注入
1.1 用法示例
// 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容器的高级特性,可以以一种 干净的方法来处理这种情况。
1.2 代码示例
1.2.1 准备Bean
package com.ws.edu.spring;
public class Game {
}
package com.ws.edu.spring;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class Person implements ApplicationContextAware{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
private Game createGame(){
return applicationContext.getBean(Game.class);
}
public void playGame(){
Game game = this.createGame();
System.out.println("playing game:"+game);
}
}
1.2.2 配置XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true">
<bean id="person" class="com.ws.edu.spring.Person"/>
<bean id="game" class="com.ws.edu.spring.Game" scope="prototype"/>
</beans>
1.2.3 配置启动类
package com.ws.edu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
@SuppressWarnings("resource")
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean(Person.class);
person.playGame();
}
}
1.2.4 输出结果
2 使用Lookup实现方法注入
Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码的功能,通过动态创建Lookup方法bean的子类从而达到复写Lookup方法的目的。
为了使动态子类起作用,Spring容器要子类化的类不能是
final
,并且需要复写的方法也不能是final
。同样的,要测试一个包含抽象
方法的类也稍微有些不同,你需要子集编写它的子类提供该抽象
方法的实现。最后,作为方法注入目标的bean不能是序列化的。 在Spring 3.2之后再也没必要添加CGLIB到classpath,因为CGLIB的类打包在了org.springframework下并且在Spring核心JAR中有所描述。 这样做既方便,又避免了与其他使用了不同版本CGLIB的项目的冲突。
2.1 用法示例
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);
public|protected要求方法必须是可以被子类重写和调用的;
abstract可选,如果是抽象方法,CGLIB的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法,所以没什么影响;
return-type就是non-singleton-bean的类型咯,当然可以是它的父类或者接口。
no-arguments不允许有参数。
如果方法是抽象
,动态生成的子类会实现该方法。沟则,动态生成的子类会覆盖类里的具体方法。譬如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" 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="command"/>
</bean>
标识为_commandManager_的bean在需要一个新的_command_ bean实例时会调用createCommand()
方法。你必须将`command`bean部署为 原型(prototype)类型,如果这是实际需要的话。如果部署为singleton。那么每次将返回相同的 `command`bean。
2.2 代码示例
2.2.1 准备Bean
package com.ws.edu.spring;
public class Game {
}
package com.ws.edu.spring;
public class Person{
public Game createGame(){
return null;
}
public void playGame(){
Game game = this.createGame();
System.out.println("playing game:"+game);
}
}
2.2.2 配置Xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true">
<bean id="person" class="com.ws.edu.spring.Person">
<lookup-method bean="game" name="createGame"/>
</bean>
<bean id="game" class="com.ws.edu.spring.Game" scope="prototype"/>
</beans>
2.2.3 编写启动类
package com.ws.edu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
@SuppressWarnings("resource")
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean(Person.class);
person.playGame();
}
}
2.2.4 输出结果
3 自定义方法的替代方案
3.1 用法示例
使用基于XML配置文件时,你可以使用replaced-method
元素来达到用另一个方法来取代已有方法的目的。考虑下面的类,我们将覆盖 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
因为参数的数目通常足够用来区别每个可能的选择,这个结晶能减少很多键盘输入的工作,它允许你只输入最短的匹配参数类型的字符串。
3.2 代码示例
3.2.1 准备Bean
package com.ws.edu.spring;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
public class Game implements MethodReplacer{
@Override
public Object reimplement(Object obj, Method method, Object[] args)
throws Throwable {
String gameName = (String)args[0];
System.out.println("playing game:" + gameName);
return null;
}
}
package com.ws.edu.spring;
public class Person{
public void playGame(String gameName){}
}
3.2.2 配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true">
<bean id="person" class="com.ws.edu.spring.Person">
<replaced-method name="playGame" replacer="game">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="game" class="com.ws.edu.spring.Game"/>
</beans>
3.2.3 编写启动类
package com.ws.edu.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
@SuppressWarnings("resource")
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean(Person.class);
person.playGame("篮球");
}
}