Spring源码(二-2)-lookup-method、replaced-method标签

1,222 阅读3分钟

lookup-method

通常称为获取器注入,spring in action 中对它的描述是,一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean,而实际要返回的 bean 是在配置文件里面配置的,可用在设计可插拔的功能上,解除程序依赖。

实例

  1. 首先创建一个父类,并编写一个方法 eat()。
/**
 * 父类:水果
 */
public class Fruit {

	public void eat(){
		System.out.println("吃什么水果呢");
	}
}

2、然后创建一个子类,继承父类,并重写父类的方法。

/**
 * 子类:苹果
 */
public class Apple extends Fruit {

	@Override
	public void eat(){
		System.out.println("吃苹果");
	}
}

3、创建调用方法

public abstract class TestLookupMethod {

	public abstract Fruit getBean();
    
	/**
	 * 这个方法的创建不会对于 LookupMethod 的覆盖不会有任何影响
	 * 这个参数 Spring 并不会去处理它
	 * 影响的地方也就是在 createBean() 里的 prepareMethodOverrides() 方法了,
	 * overloaded 属性不会被设置为 false 了。

AbstractBeanDefinition#prepareMethodOverride
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
		// 获取对应类中对应方法的个数
		int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
		if (count == 0) {
			throw new BeanDefinitionValidationException(
					"Invalid method override: no method with name '" + mo.getMethodName() +
					"' on class [" + getBeanClassName() + "]");
		}
		else if (count == 1) {
			// Mark override as not overloaded, to avoid the overhead of arg type checking.
			//标记 overloaded 暂未被覆盖 避免参数类型检查的开销
			mo.setOverloaded(false);
		}
	}
	 */
	public abstract Fruit getBean(String str);
    
    
	public void start(){
		this.getBean().eat();
	}
}

4、然后编写配置文件

<bean id="fruit" class="com.gongj.lookupMethod.Fruit"></bean>
<bean id="apple" class="com.gongj.lookupMethod.Apple"></bean>

<bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
    // name 值为 getBean
	<lookup-method name="getBean" bean="apple"></lookup-method>
</bean>

5、进行测试

	public static void main(String[] args) {
		ClassPathXmlApplicationContext context =
				new ClassPathXmlApplicationContext("lookup-method.xml");
		TestLookupMethod bean =(TestLookupMethod) context.getBean("lookupMethod");
        // 调用 start 方法
		bean.start();
	}
输出结果:吃苹果

当业务变更或者在其它情况下, apple 里面的业务逻辑已不再符合我们的业务要求,需要进行替换怎么办 ?这时我们需要新增逻辑类。

/**
 * 子类:香蕉
 */
public class Banana extends Fruit{

	@Override
	public void eat(){
		System.out.println("正在吃香蕉");
	}
}

然后只需修改配置类,新增名为 banana 的 Bean,并将lookup-method标签中配置的 apple 修改为banana。再次调用测试方法。

<bean class="com.gongj.lookupMethod.Banana" id="banana"></bean>
	<bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
		<lookup-method name="getBean" bean="banana"></lookup-method>
	</bean>

Spring 框架通过使用 CGLIB 库中的字节码来动态生成覆盖该方法的子类,从而实现此方法注入。所以被覆盖的类不能为 final,并且要被覆盖的方法也不能为 final 。还需注意 scope 的配置,如果 scope 配置为 singleton,则每次调用 getBean 方法 ,返回的对象都是相同的;如果 scope 配置为 prototype,则每次调用返回都不同。

在包含要注入的方法的 TestLookupMethod 类中,要注入的方法(getBean)需要以下形式的签名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

  • public|protected:要求方法必须是可以被子类重写和调用的。
  • abstract:可选,如果是抽象方法,CGLIB 的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法。
  • return-type:返回类型,当然可以是它的父类或者接口。
  • no-arguments:不允许有参数。

到这,我们已经会使用 lookup-method 这个标签了,接下来我们就结合源码去看。直接进入到BeanDefinitionParserDelegate 类的 parseLookupOverrideSubElements方法。

	public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
		// 获取该bean节点下所有子节点
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			//获取每个子节点
			Node node = nl.item(i);
			// 子元素为 lookup-method 时才有效
			if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
				Element ele = (Element) node;
				// 获取 name 属性的值,也就是要修饰的方法
				String methodName = ele.getAttribute(NAME_ATTRIBUTE);
				// 获取 bean 属性的值,也就是配置返回的bean 名称
				String beanRef = ele.getAttribute(BEAN_ELEMENT);
				// 根据 methodName、beanRef 构建 LookupOverride 对象
				LookupOverride override = new LookupOverride(methodName, beanRef);
				override.setSource(extractSource(ele));
				// 将该对象添加到 MethodOverrides对象里的 overrides集合中
				overrides.addOverride(override);
			}
		}
	}

replaced-method

方法替换:可以在运行时用新的方法替换现有的方法,与之前的 look-up 不同的是,replaced-method 不但可以动态地替换返回实体 bean ,而且还能动态地更改原有方法的逻辑。我们先来看任何使用。

实例

1、再次新创建一个类,里面提供两个重载方法。


public class Replaced {

	public void replacedName(String name){
		System.out.println("我是初版:"+name);
	}

	public void replacedName(String name,Integer age){
		System.out.println("我是初版:" + name + "年年:"+ age);
	}
}

2、编写一个实现了 MethodReplacer 的类

public class TestReplacedMethod implements MethodReplacer {
	@Override
	public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
		for (Object arg : args) {
			System.out.println("接收参数:" + arg);
		}
		return null;
	}
}

3、修改配置文件

<bean class="com.gongj.lookupMethod.Replaced" id="replaced">
		<replaced-method name="replacedName" replacer="replacedMethod">
<!--<arg-type></arg-type>可以配置任意多个 取决于想替换哪个重载方法-->
			<arg-type>String</arg-type>
			<arg-type>Integer</arg-type>
		</replaced-method>
	</bean>

	<!-- replacedMethod -->
	<bean class="com.gongj.lookupMethod.TestReplacedMethod" id="replacedMethod"></bean>

4、测试类

	public static void main(String[] args) {
		ClassPathXmlApplicationContext context =
				new ClassPathXmlApplicationContext("replace-method.xml");
		Replaced bean =(Replaced) context.getBean("replaced");
		bean.replacedName("gongj",88);

	}
输出结果:
接收参数:gongj
接收参数:88

运行测试类就可以看到预期的结果了,也就是做到了动态替换原有方法,知道了这个元素的用法,再来看元素的提取过程。

直接进入到BeanDefinitionParserDelegate 类的 parseReplacedMethodSubElements方法。

public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
		// 获取该bean节点下所有子节点
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			//获取每个子节点
			Node node = nl.item(i);
			// 子元素为 replaced-method 时才有效
			if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
				Element replacedMethodEle = (Element) node;
				// 获取 name 属性的值,也就是要被替换的旧方法
				String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
				// 获取 replacer 属性的值,也就是新的替换方法
				String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
				// 根据 name、replacer 构建 ReplaceOverride 对象
				ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
				// Look for arg-type match elements. 寻找arg-type元素
				List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
				for (Element argTypeEle : argTypeEles) {
					// 记录参数
					String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
					match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
					if (StringUtils.hasText(match)) {
						replaceOverride.addTypeIdentifier(match);
					}
				}
				replaceOverride.setSource(extractSource(replacedMethodEle));
				// 将该对象添加到 MethodOverrides对象里的 overrides集合中
				overrides.addOverride(replaceOverride);
			}
		}
	}

我们可以看到无论是 lookup-method 还是 replaced-method 都是构造了 MethodOverride,并最终记录在了 AbstractBeanDefinition 中的 methodOverrides 属性中。

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。