美女邀我去歌舞廳娛樂,我拒絕了,我以爲跟技術宅男們分享技術更爲重要。
java
Spring方法注入的概念:一個由容器管理的singleton bean中,須要引入另一個由容器管理的prototype bean,因爲singleton bean只會被建立一次,如何保證singleton bean每次調用時用到的prototype bean必定是prototype的呢(存在prototype被錯誤注入成singleton的風險)?因而,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入),來解決此類問題。spring
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(查找方法注入)兩種解決方案。
測試
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(查找方法注入),這纔是咱們探究的重點。
首先,咱們經過代碼來看看,咱們要實現的功能。首先定義一個抽象類,提供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的具體源碼實現。
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源碼的絆腳石或障礙。
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)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)