Spring查找方法注入(Lookup method injection)的底层实现原理

拟墨画扇 提交于 2019-12-06 16:16:42

美女邀我去歌舞厅娱乐,我拒绝了,我觉得跟技术宅男们分享技术更为重要。

Spring方法注入的概念:一个由容器管理的singleton bean中,需要引入另外一个由容器管理的prototype bean,由于singleton bean只会被创建一次,如何保证singleton bean每次调用时用到的prototype bean一定是prototype的呢(存在prototype被错误注入成singleton的风险)?于是,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入),来解决此类问题。

1.错误使用prototype的例子

public interface Command {
	
	public Object execute();
	
}

再定义一个实现类,prototype类型。

public class AsyncCommand implements Command {

	@Override
	public Object execute() {
		System.out.println("Async command execute.");
		return "Execute result.";
	}
}

在一个singleton bean中使用该prototype类。

public class Manager {
	
	private AsyncCommand command;

	// inject
	public void setCommand(AsyncCommand command) {
		this.command = command;
	}
	
	public void process() {
		command.execute();
		System.out.println(command); 
	}
}

Spring的Xml配置文件如下:

<bean id="command" class="x.y.AsyncCommand" scope="prototype" />
	
<bean id="manager" class="x.y.Manager" scope="singleton">
	<property name="command" ref="command" />
</bean>

写一个方法来测试它。

public static void main(String[] args) {

	FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
			"D:/workspace/Spring4.2.5/bin/context.xml");
		
	Manager m = context.getBean("manager", Manager.class);
	for (int i = 0; i < 5; i++) {
		m.process();
	}

	context.close();
}

output:

x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.

五次全是x.y.AsyncCommand@5891e32e,说明Command根本不是prototype,而变成了singleton了。这和XML中定义的scope="prototype"相违背。



为了解决上述问题,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入)两种解决方案。

2.Method injection(方法注入)

Method injection解决方案很简单,直接上代码。

public class Manager implements ApplicationContextAware {

	private ApplicationContext applicationContext;

	public void process() {
		Command command = applicationContext.getBean("command", Command.class);
		command.execute();
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

Method injection是一种解决方案,另外一种解决方案是Lookup method injection(查找方法注入),这才是我们探究的重点。

3.Lookup method injection(查找方法注入)(本文讨论的重点)

1.使用Cglib自己编程实现Lookup method injection(查找方法注入)

首先,我们通过代码来看看,我们要实现的功能。首先定义一个抽象类,提供Command对象的createCommand()方法是一个abstract抽象方法。

public abstract class CommandManager {

	public Object process() {
		Command command = createCommand();
		System.out.println(command);
		return command.execute();
	}

	public abstract Command createCommand();

}

要求创建一个CommandManager实例,并准确提供prototype类型的Command对象。注意那个createCommand()方法。

我们来编写一个自定义的LookupOverrideMethodInterceptor拦截器,来完成此功能。

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class LookupOverrideMethodInterceptor implements MethodInterceptor {

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
	        // 检测是否是需要override的方法(写的很简单,能说明问题即可,要那么复杂干嘛呢)
		if ("createCommand".equals(method.getName())) {
			return new AsyncCommand();
		}
		return methodProxy.invokeSuper(obj, args);
	}
}

再编写一个测试类。

public static void main(String[] args) {
	Enhancer en = new Enhancer();
	en.setSuperclass(CommandManager.class);
	en.setCallback(new LookupOverrideMethodInterceptor());
		
	CommandManager cm = (CommandManager) en.create();
	for (int i = 0; i < 5; i++) {
		cm.process();
	}
}

output:

x.y.AsyncCommand@736e9adb
Async command execute.
x.y.AsyncCommand@6d21714c
Async command execute.
x.y.AsyncCommand@108c4c35
Async command execute.
x.y.AsyncCommand@4ccabbaa
Async command execute.
x.y.AsyncCommand@4bf558aa
Async command execute.

我们自定义的LookupOverrideMethodInterceptor拦截器,就轻松的完成了查找方法注入,这就是大名鼎鼎的Spring其Lookup method injection(查找方法注入)的底层实现原理。

以上是通过cglib编程方式,自己实现的Lookup method injection(查找方法注入)。



在Spring中使用则比较简单了,其Xml文件配置如下:

<bean id="command" class="x.y.AsyncCommand" scope="prototype" />
	
<bean id="commandManager" class="x.y.CommandManager">
	<lookup-method name="createCommand" bean="command" />
</bean>

明白了原理之后,那就让我们来看看Spring的具体源码实现。

2.Spring查找方法注入(Lookup method injection)的源码解析

org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy策略类的部分源码。

    public Object instantiate(Constructor<?> ctor, Object... args) {
                        // 创建Cglib的代理对象的Class
			Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
			Object instance;
			if (ctor == null) {
			        // 通过代理对象Class反射创建Cglib代理对象
				instance = BeanUtils.instantiate(subclass);
			}
			else {
				try {
					Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
					instance = enhancedSubclassConstructor.newInstance(args);
				}
				catch (Exception ex) {
					throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
							"Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
				}
			}
			Factory factory = (Factory) instance;
			// 注册LookupOverrideMethodInterceptor和ReplaceOverrideMethodInterceptor(注册了2个,是不是2个都回调呢?非也,MethodOverrideCallbackFilter会正确选择其中1个来回调,二选一)
			factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
					new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
					new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
			return instance;
		}

		/**
		 * Create an enhanced subclass of the bean class for the provided bean
		 * definition, using CGLIB.
		 */
		private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(beanDefinition.getBeanClass());
			enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
			if (this.owner instanceof ConfigurableBeanFactory) {
				ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();
				enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));
			}
			// 注册Callback过滤器(选择使用LookupOverrideMethodInterceptor,还是ReplaceOverrideMethodInterceptor,就是靠该过滤器控制的)
			enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));
			enhancer.setCallbackTypes(CALLBACK_TYPES);
			return enhancer.createClass();
		}

Spring中LookupOverrideMethodInterceptor的源码。

private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
                // DefaultListableBeanFactory容器对象
		private final BeanFactory owner;

		public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
			super(beanDefinition);
			this.owner = owner;
		}

		@Override
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
			// Cast is safe, as CallbackFilter filters are used selectively.
			LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
			Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at all
			if (StringUtils.hasText(lo.getBeanName())) {
			        // 到容器中,通过xml配置的bean name去查找依赖的prototype对象
				return this.owner.getBean(lo.getBeanName(), argsToUse);
			}
			else {
			        // 到容器中,通过方法的返回类型去查找依赖的prototype对象
				return this.owner.getBean(method.getReturnType(), argsToUse);
			}
		}
	}

分析到此,是不是对Lookup method injection(查找方法注入)的底层实现原理彻底清楚了呢?这些知识点其实并不重要,分析它,是避免它成为我们理解、阅读Spring源码的绊脚石或障碍。

3.Arbitrary method replacement(强行替换注入)

Arbitrary method replacement(强行替换注入)和Lookup method injection(查找方法注入)的原理是一模一样的,就是叫法和用途不同而已。

强行替换注入的含义是:我不要原来的bean提供方式了,我要新的提供方式来替换原来的提供方式。

private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {

		private final BeanFactory owner;

		public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
			super(beanDefinition);
			this.owner = owner;
		}

		@Override
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
			ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
			// TODO could cache if a singleton for minor performance optimization
			MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);
			return mr.reimplement(obj, method, args);
		}
	}

LookupOverrideMethodInterceptor和ReplaceOverrideMethodInterceptor两兄弟的实现原理,真的就是一模一样,靠的就是Cglib拦截器。


总结:我们不仅分析了三种特殊注入方式的来历,还亲自使用Cglib手工实现了Lookup method injection,明白了其底层原理。最后,还分析了Spring的源码。这些知识点确实是几乎不使用的,但是,理解其原理,有助于我们快速跳过这些障碍,去学习和理解Spring其他重要且实用的功能。

但是,也不要小看这些技术思想,在Mybatis中,我们定义一个interface的接口UserMapper.java,没有任何实现类的情况下,Mybatis居然可以提供一个接口UserMapper的实例,并可以调用实例里面的方法返回真实的数据库数据给我们使用,你不觉得很奇怪吗?我想,读懂了本篇文章,自然就能对Mybatis的实现原理猜出一二,敬请期待后续博文,对Mybatis的底层原理探究吧。


注:Spring4已经完全内置了Cglib的功能,已经不再需要额外的Cglib的jar包了,本人使用的是Spring4.2.5版本。因此,请用您明亮的慧眼,去区分是cglib包中的类,还是Spring中的cglib相关类。


版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!