Spring中引入增強(IntroductionAdvice)的底層實現原理

作爲一名程序員,輿論一直在告誡我,要有自己的博客,要記錄並懂得和別人分享你的技術心得。然而,遺憾的是,每當我有所收穫,併爲之興奮,準備下筆要和別人分享之時,我驀然發現,已經有N位程序員同仁,在N年前,就已經記錄過並分享過無數多次該技術要點了。此時,我又何必多此一舉的弄一個N+1呢?多年過去,依舊是白紙一張。

不過,在等待多年後,我已經按耐不住了,我的機會終於來了,這一次,無論外面如何春暖花開,如何豔陽高照,如何美女如雲,我都視之如糞土。現在,我要做的是,與君分享技術心得。

 

Let's Go(來,死狗).

Spring中有五種增強:BeforeAdvide(前置增強)、AfterAdvice(後置增強)、ThrowsAdvice(異常增強)、RoundAdvice(環繞增強)、IntroductionAdvice(引入增強)

RoundAdvice(環繞增強):就是BeforeAdvide(前置增強)、AfterAdvice(後置增強)的組合使用叫環繞增強。

前四種增強都比較簡單,我們今天要介紹的是IntroductionAdvice(引入增強)的概念及原理。

 

引入增強(Introduction Advice)的概念:一個Java類,沒有實現A接口,在不修改Java類的情況下,使其具備A接口的功能。

1.Cglib實現引入增強

記住,我的目的不是告訴你怎麼在Spring中使用引入增強功能(這不是我的風格),而是探究引入增強功能的底層實現原理。

public interface IHello {

	public void sayHello();
}

上面是接口功能,CeremonyService是需要增強的類,在不改變CeremonyService類的情況下,使其具備IHello接口功能。

public class CeremenyService {
	
	public void sayBye() {
		System.out.println("Say bye from Ceremeny.");
	}
	
}

看起來要像下面這樣:

CeremenyService cs;
		
IHello ih = (IHello) cs;
ih.sayHello();

即,CeremenyService居然變成了IHello類型。

我們編寫一個重要的攔截器,來實現此功能。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import x.y.IHello;

public class IntroInterceptor implements MethodInterceptor, IHello {
        // 實現了IHello增強接口的對象
	private Object delegate;

	public IntroInterceptor() {
		this.delegate = this;
	}

	public IntroInterceptor(Object delegate) {
		this.delegate = delegate;
	}

	@Override
	public void sayHello() {
		System.out.println("Say hello from delegate.");
	}

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		Class<?> clz = method.getDeclaringClass();
		if (clz.isAssignableFrom(IHello.class)) {
		        // 如果實現了IHello增強接口,則調用實現類delegate的方法
			return method.invoke(delegate, args);
		}
		return methodProxy.invokeSuper(obj, args);
	}
}

我們來編寫一個測試類。

public static void main(String[] args) {
	Enhancer en = new Enhancer();
	en.setSuperclass(CeremenyService.class);
	en.setInterfaces(new Class[] { IHello.class });
	en.setCallback(new IntroInterceptor());

	CeremenyService cs = (CeremenyService) en.create();
	cs.sayBye();
		
	IHello ih = (IHello) cs;
	ih.sayHello();
}

en.setInterfaces(new Class[] { IHello.class });非常重要,表示Cglib生成代理類,將要實現的接口集合。

於是生成的代理類Class,類似於:public class CeremenyService$$EnhancerByCGLIB$$86859be5 extends CeremenyService implements IHello

輸出結果:

Say bye from Ceremeny.
Say hello from delegate.

這就是大名鼎鼎的引入增強(Introduction Advice)的底層實現原理。

2. Spring framework引入增強源碼解讀

    Spring的xml文件配置。

<bean id="ceremonyService" class="x.y.service.CeremonyService" />
<bean id="ceremonyIntroAdvice" class="x.y.advice.CeremonyIntroAdvice" />

<bean id="ceremonyProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="interfaces" value="x.y.IHello"/>                   <!-- 需要動態實現的接口 -->
        <property name="target" ref="ceremonyService"/>                    <!-- 目標類 -->
        <property name="interceptorNames" value="ceremonyIntroAdvice"/>    <!-- 引入增強 -->
        <property name="proxyTargetClass" value="true"/>                   <!-- 代理目標類(默認爲 false,代理接口) -->
</bean>

我們需要自定義一個攔截器。

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import x.y.IHello;

@SuppressWarnings("serial")
public class CeremonyIntroAdvice extends DelegatingIntroductionInterceptor implements IHello {
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		return super.invoke(mi);
	}

	@Override
	public void sayHello() {
		System.out.println("Say hello.");
	}

}

在Spring中,要實現引入增強,需要繼承自DelegatingIntroductionInterceptor。

下面看看該DelegatingIntroductionInterceptor類的invoke()方法源碼。

@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
	        // 檢測是否是引入增強
		if (isMethodOnIntroducedInterface(mi)) {
			// 執行實現了引入增強接口的delegate對象的增強方法
			Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());

			// Massage return value if possible: if the delegate returned itself,
			// we really want to return the proxy.
			if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
				Object proxy = ((ProxyMethodInvocation) mi).getProxy();
				if (mi.getMethod().getReturnType().isInstance(proxy)) {
					retVal = proxy;
				}
			}
			return retVal;
		}

		return doProceed(mi);
	}

AopUtils.invokeJoinpointUsingReflection()方法內部,其實就是反射方法調用。

try {
	ReflectionUtils.makeAccessible(method);
	return method.invoke(target, args);
}

最後寫一個測試方法,來測試一下。

public static void main(String[] args) {

	FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
			"D:/workspace/Spring4.2.5/bin/applicationContext.xml");

	CeremonyService service = context.getBean("ceremonyProxy", CeremonyService.class);
	service.sayBye();
		
	IHello hello = (IHello) service;
	hello.sayHello();
		
	context.close();
}

輸出:

Say bye.
Say hello.

 

總結:介紹如何使用的文章比較多,而介紹原理性的文章少一些,我比較喜歡介紹原理性的文章。希望本篇博文,對您有用。

 

版權提示:文章出自開源中國社區,若對文章感興趣,可關注我的開源中國社區博客(http://my.oschina.net/zudajun)。(經過網絡爬蟲或轉載的文章,經常丟失流程圖、時序圖,格式錯亂等,還是看原版的比較好)

發佈了28 篇原創文章 · 獲贊 125 · 訪問量 125萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章