Spring 源码分析衍生篇三 : lookup-method 和 replaced-method

一、前言

本文是 Spring源码分析:单例bean的获取 - createBean 的衍生文章。主要是因为本人菜鸡,在分析源码的过程中还有一些其他的内容不理解,故开设衍生篇来完善内容以学习。

二、基本使用

1. 作用

  • lookup-method :用于注入方法返回结果,也就是说能通过配置方式替换方法返回结果。(在方法或者抽象方法上使用@Lookup注解,将会根据该方法的返回值,自动在BeanFactory中调用getBean()来注入该Bean)
  • replaced-method :可以实现方法主体或返回结果的替换
    通俗来讲 : lookup-method 可以注入属性bean, replaced-method 替换方法实现。

下面跟着一个Demo来理解

// 基类接口
public interface DemoBase {
    String hello();
}

...
public class DemoA implements DemoBase {
    public DemoBase getDemoBase() {
        return new DemoB();
    }

    @Override
    public String hello() {
        return "demoA hello";
    }
}
...
public class DemoB implements DemoBase {
    @Override
    public String hello() {
        return "DemoB hello";
    }
}
...
public class DemoC implements DemoBase{
    @Override
    public String hello() {
        return "DemoC hello";
    }
}
....

public class DemoMethodReplacer implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("method = " + method);
        return "reimplement";
    }
}

我们来配置一下bean.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">
    <bean id="demoA"  name="demoA"  class="com.kingfish.springbootdemo.property.DemoA">
    	// 指定 DemoA.getDemoBase 方法,返回值是 容器中的demoC
        <lookup-method name="getDemoBase" bean="demoC"></lookup-method>
        // 指定 DemoA.hello方法,其方法经过 demoMethodReplacer.reimplement 代理
        <replaced-method name="hello" replacer="demoMethodReplacer"></replaced-method>
    </bean>
    <bean id="demoB" name="demoB" class="com.kingfish.springbootdemo.property.DemoB"></bean>
    <bean id="demoC" name="demoC" class="com.kingfish.springbootdemo.property.DemoC"></bean>
    <bean id="demoMethodReplacer" name="demoMethodReplacer" class="com.kingfish.springbootdemo.property.DemoMethodReplacer"></bean>
</beans>
@SpringBootApplication
@ImportResource("bean.xml")
public class SpringbootDemoApplication implements ApplicationRunner {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemoApplication.class, args);
        DemoA demoA = run.getBean(DemoA.class);
        System.out.println(demoA.hello());
        System.out.println(demoA.getDemoBase());

}

运行后输出结果如下
在这里插入图片描述

根据上面的demo。我们可以看出

  • lookup-method : name 指定方法名,bean 指定方法返回的bean。这里注意上面的demo中getDemoBase方法没有必要实现,可以直接写一个抽象方法。即当调用 demoA.getDemoBase 方法时返回的是 Spring 容器中name为demoC 的bean。这里可以通过将demoC 设置成原型模式来实现单例模式下原型bean的获取。
  • replaced-mthod : name 指定方法名,replacer 指定代理类。即当调用demoA中的hello方法时,会经过demoMethodReplacer的代理。demoMethodReplacer需要实现MethodReplacer 接口,调用方法时会经过MethodReplacer.reimplement 方法。

三、原理实现

首先需要知道,lookup-method 和 replaced-method 注解标注的方法都会被保存在BeanDefinition.methodOverrides 集合中。这两个功能的实现原理其实也是在bean实例化的时候如果检测到 methodOverrides 属性存在,则会动态的为当前bean生成代理并使用对应的拦截器为bean做增强处理。
在这里插入图片描述

Spring 对 这两个属性的处理 在两个部分

1. 预处理

1.1 AbstractBeanDefinition#prepareMethodOverrides

这个阶段的时候bean还没开始创建,先做了一个预先的工作,减少后面的工作量。

这个方法做了一些预处理的工作

详细代码如下:

	public void prepareMethodOverrides() throws BeanDefinitionValidationException {
		// Check that lookup methods exist and determine their overloaded status.
		// 判断是否有方法需要重写
		if (hasMethodOverrides()) {
			getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride);
		}
	}

	protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
		// 获取对应的类中的对应方法名的个数
		int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
		// 等于0抛出异常
		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.
			// 标记 MethodOverride 暂未被覆盖,避免参数类型检查的开销。
			mo.setOverloaded(false);
		}
	}

解释一下上面的逻辑:

  1. 首先会判断是否有方法需要重写,这里的是根据 RootBeanDefinition 中的 methodOverrides 属性来进行判断,为空则表示没有。
  2. 若上述判断有方法需要覆盖,则会调用prepareMethodOverride(MethodOverride mo) 方法。而在 prepareMethodOverride(MethodOverride mo) 方法中会根据 需要覆盖的方法名称 来获取加载类中关于该方法的实现。如果获取不到 count == 0,则直接抛出异常,如果获取到只有一个 count == 1,则记录该方法并未被重载(因为Spring在方法匹配时,如果一个类中存在若干个重载方法,则在函数调用及增强的时候需要根据参数类型进行匹配,来最终确定调用的方法是哪一个,这里直接设置了该方法并未被重载,在后续方法匹配的时候就不需要进行参数匹配验证,直接调用即可)。
  3. 打个比方,比如指定覆盖A类中的 a方法,但是A类中可能存在多个a方法或者不存在a方法,若count == 0不 存在a方法,则谈何覆盖,直接抛出异常,若count ==1 则a方法的实现只有一个,标记该方法并未被重载后续可跳过参数验证的步骤。

1.2 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors

在 这里对 @Lookup 注解 进行了一个简单处理,将被 @Lookup 注解修饰的方法添加到RootBeanDefinition 中的 methodOverrides 属性中

	public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
			throws BeanCreationException {

		// Let's check for lookup methods here...
		if (!this.lookupMethodsChecked.contains(beanName)) {
			if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) {
				try {
					Class<?> targetClass = beanClass;
					do {
						ReflectionUtils.doWithLocalMethods(targetClass, method -> {
							Lookup lookup = method.getAnnotation(Lookup.class);
							if (lookup != null) {
								Assert.state(this.beanFactory != null, "No BeanFactory available");
								LookupOverride override = new LookupOverride(method, lookup.value());
								try {
									RootBeanDefinition mbd = (RootBeanDefinition)
											this.beanFactory.getMergedBeanDefinition(beanName);
									mbd.getMethodOverrides().addOverride(override);
								}
								catch (NoSuchBeanDefinitionException ex) {
									throw new BeanCreationException(beanName,
											"Cannot apply @Lookup to beans without corresponding bean definition");
								}
							}
						});
						targetClass = targetClass.getSuperclass();
					}
					while (targetClass != null && targetClass != Object.class);

				}
				catch (IllegalStateException ex) {
					throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
				}
			}
			this.lookupMethodsChecked.add(beanName);
		}
		
	...

}

2. 真正处理

这个阶段bean处于正在创建的过程中,这里是对其他配置的处理,lookup-method,replaced-method就是其中之一。

SimpleInstantiationStrategy#instantiate(RootBeanDefinition, String, BeanFactory)

	public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		// 判断是否有使用lookup-method,replaced-method 来标注方法。如果没有直接反射即可。
		if (!bd.hasMethodOverrides()) {
			Constructor<?> constructorToUse;
			synchronized (bd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					final Class<?> clazz = bd.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						if (System.getSecurityManager() != null) {
							constructorToUse = AccessController.doPrivileged(
									(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
						}
						else {
							constructorToUse = clazz.getDeclaredConstructor();
						}
						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Throwable ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}

这里简单将一下逻辑,不再详细分析代码。
首先判断当前bean是否有使用 lookup-method,replaced-method 属性,若没有使用,则直接使用反射即可。若使用,则不能简单的使用反射,需要将这两个配置提供的功能切入进去,所以就必须使用动态代理的方式将两个特性所对应的逻辑的拦截增强器设置进去。这样就可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。


以上:内容部分参考
《Spring源码深度解析》
https://blog.csdn.net/lyc_liyanchao/article/details/82901216
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

当然,除了使用注解方式,我们还可以使用XML配置来实现Spring Lookup Method。下面是一个使用XML配置的示例: 首先,定义一个抽象类: ``` public abstract class AbstractBean { public void doSomething() { getDependency().execute(); } public abstract Dependency getDependency(); } ``` 接下来,定义一个具体的子类: ``` public class ConcreteBean extends AbstractBean { private Dependency dependency; @Override public Dependency getDependency() { if (dependency == null) { dependency = createDependency(); } return dependency; } protected Dependency createDependency() { // 返回一个新的Dependency对象 } } ``` 注意到这里的`createDependency()`方法没有被`@Lookup`注解标记,这是因为我们将会在XML中配置这个方法。 接下来,我们需要在XML中配置这个类: ``` <bean id="concreteBean" class="com.example.ConcreteBean"> <lookup-method name="createDependency" bean="dependency"/> </bean> <bean id="dependency" class="com.example.Dependency"/> ``` 注意到这里的`lookup-method`元素,它告诉Spring在运行时为`createDependency()`方法生成一个代理,并将其返回值注入到`ConcreteBean`对象中。 现在,我们可以在其他类中注入`concreteBean`对象,并调用它的`doSomething()`方法,它将会使用`ConcreteBean`中的`createDependency()`方法来获取一个新的`Dependency`对象,并执行它的`execute()`方法。 这就是使用XML配置的Spring Lookup Method的一个简单示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值