作爲一名程序員,輿論一直在告誡我,要有自己的博客,要記錄並懂得和別人分享你的技術心得。然而,遺憾的是,每當我有所收穫,併爲之興奮,準備下筆要和別人分享之時,我驀然發現,已經有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)。(經過網絡爬蟲或轉載的文章,經常丟失流程圖、時序圖,格式錯亂等,還是看原版的比較好)