Spring查找方法注入(Lookup method injection)的底層實現原理

美女邀我去歌舞廳娛樂,我拒絕了,我以爲跟技術宅男們分享技術更爲重要。
java

Spring方法注入的概念:一個由容器管理的singleton bean中,須要引入另一個由容器管理的prototype bean,因爲singleton bean只會被建立一次,如何保證singleton bean每次調用時用到的prototype bean必定是prototype的呢(存在prototype被錯誤注入成singleton的風險)?因而,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入),來解決此類問題。spring

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>

寫一個方法來測試它。
app

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:ide

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解決方案很簡單,直接上代碼。
this

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)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)

相關文章
相關標籤/搜索