轉(zhuǎn)帖|其它|編輯:郝浩|2011-03-24 13:19:42.000|閱讀 497 次
概述:文章主要和大家講解開(kāi)發(fā)應(yīng)用系統(tǒng)時(shí)在.Net語(yǔ)言中關(guān)于A(yíng)OP 的實(shí)現(xiàn)。LogAspect完成的功能主要是將Advice與業(yè)務(wù)對(duì)象的方法建立映射,并將其添加到Advice集合中。由于我們?cè)贏(yíng)OP實(shí)現(xiàn)中,利用了xml配置文件來(lái)配置PointCut,因此對(duì)于所有Aspect而言,這些操作都是相同的,只要定義了正確的配置文件,將其讀入即可。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門(mén)軟控件火熱銷(xiāo)售中 >>
文章主要和大家講解開(kāi)發(fā)應(yīng)用系統(tǒng)時(shí)在.Net語(yǔ)言中關(guān)于A(yíng)OP 的實(shí)現(xiàn)。LogAspect完成的功能主要是將Advice與業(yè)務(wù)對(duì)象的方法建立映射,并將其添加到Advice集合中。由于我們?cè)贏(yíng)OP實(shí)現(xiàn)中,利用了xml配置文件來(lái)配置PointCut,因此對(duì)于所有Aspect而言,這些操作都是相同的,只要定義了正確的配置文件,將其讀入即可。對(duì)于A(yíng)spect的SyncProcessMessage(),由于攔截和織入的方法是一樣的,不同的只是Advice的邏輯而已,因此在所有Aspect的公共基類(lèi)中已經(jīng)提供了默認(rèn)的實(shí)現(xiàn):
public class LogAspect:Aspect
{
public LogAspect(IMessageSink nextSink):base(nextSink)
{}
}
然后定義正確的配置文件:
<aspect value ="LogAOP">
<advice type="before" assembly=" AOP.Advice" class="AOP.Advice.LogAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
</advice>
<advice type="after" assembly=" AOP.Advice" class="AOP.Advice.LogAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
</advice>
</aspect>
LogAdvice所屬的程序集文件為AOP.Advice.dll,完整的類(lèi)名為AOP.Advice.LogAdvice。
日志Advice(LogAdvice)
由于日志方面需要記錄方法調(diào)用前后的相關(guān)數(shù)據(jù),因此LogAdvice應(yīng)同時(shí)實(shí)現(xiàn)IBeforeAdvice和IAfterAdvice接口:
public class LogAdvice:IAfterAdvice,IBeforeAdvice
{
#region IBeforeAdvice Members
public void BeforeAdvice(IMethodCallMessage callMsg)
{
if (callMsg == null)
{
return;
}
Console.WriteLine("{0}({1},{2})",
callMsg.MethodName, callMsg.GetArg(0),
callMsg.GetArg(1));
}
#endregion
#region IAfterAdvice Members
public void AfterAdvice(IMethodReturnMessage returnMsg)
{
if (returnMsg == null)
{
return;
}
Console.WriteLine("Result is {0}", returnMsg.ReturnValue);
}
#endregion
}
在BeforeAdvice()方法中,消息類(lèi)型為IMethodCallMessage,通過(guò)這個(gè)接口對(duì)象,可以獲取方法名和方法調(diào)用的參數(shù)值。與之相反,AfterAdvice()方法中的消息類(lèi)型為IMethodReturnMessage,Advice所要獲得的數(shù)據(jù)為方法的返回值ReturnValue。
性能監(jiān)測(cè)方面
性能監(jiān)測(cè)方面與日志方面的實(shí)現(xiàn)大致相同,為簡(jiǎn)便起見(jiàn),我要實(shí)現(xiàn)的性能監(jiān)測(cè)僅僅是記錄方法調(diào)用前和調(diào)用后的時(shí)間。
性能監(jiān)測(cè)Attribute(MonitorAOPAttribute)
與日志Attribute相同,MonitorAOPAttribute僅僅需要?jiǎng)?chuàng)建并返回對(duì)應(yīng)的MonitorAOPProperty對(duì)象:
[AttributeUsage(AttributeTargets.Class)]
public class MonitorAOPAttribute:AOPAttribute
{
public MonitorAOPAttribute():base()
{}
public MonitorAOPAttribute(string aspectXml):base(aspectXml)
{}
protected override AOPProperty GetAOPProperty()
{
return new MonitorAOPProperty();
}
性能監(jiān)測(cè)Property(MonitorAOPProperty)
MonitorAOPProperty的屬性名將定義為MonitorAOP,使其與日志方面的屬性區(qū)別。除定義性能監(jiān)測(cè)方面的屬性名外,還需要重寫(xiě)CreateAspect()方法,創(chuàng)建并返回對(duì)應(yīng)的方面對(duì)象MonitorAspect:
public class MonitorAOPProperty:AOPProperty
{
protected override IMessageSink CreateAspect
(IMessageSink nextSink)
{
return new MonitorAspect(nextSink);
}
protected override string GetName()
{
return "MonitorAOP";
}
}
4.4.2.3性能監(jiān)測(cè)Aspect(MonitorAspect)
MonitorAspect類(lèi)的實(shí)現(xiàn)同樣簡(jiǎn)單:
public class MonitorAspect:Aspect
{
public MonitorAspect(IMessageSink nextSink):base(nextSink)
{}
}
而其配置文件的定義則如下所示:
<aspect value ="MonitorAOP">
<advice type="before" assembly=" AOP.Advice"
class="AOP.Advice.MonitorAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
</advice>
<advice type="after" assembly=" AOP.Advice"
class="AOP.Advice.MonitorAdvice">
<pointcut>ADD</pointcut>
<pointcut>SUBSTRACT</pointcut>
</advice>
</aspect>
MonitorAdvice所屬的程序集文件為AOP.Advice.dll,完整的類(lèi)名為AOP.Advice.MonitorAdvice。
性能監(jiān)測(cè)Advice(MonitorAdvice)
由于性能監(jiān)測(cè)方面需要記錄方法調(diào)用前后的具體時(shí)間,因此MonitorAdvice應(yīng)同時(shí)實(shí)現(xiàn)IBeforeAdvice和IAfterAdvice接口:
public class MonitorAdvice : IBeforeAdvice, IAfterAdvice
{
#region IBeforeAdvice Members
public void BeforeAdvice(IMethodCallMessage callMsg)
{
if (callMsg == null)
{
return;
}
Console.WriteLine("Before {0} at {1}",
callMsg.MethodName, DateTime.Now);
}
#endregion
#region IAfterAdvice Members
public void AfterAdvice(IMethodReturnMessage returnMsg)
{
if (returnMsg == null)
{
return;
}
Console.WriteLine("After {0} at {1}",
returnMsg.MethodName, DateTime.Now);
}
#endregion
}
MonitorAdvice只需要記錄方法調(diào)用前后的時(shí)間,因此只需要分別在BeforeAdvice()和AfterAdvice()方法中,記錄當(dāng)前的時(shí)間即可。
業(yè)務(wù)對(duì)象與應(yīng)用程序
業(yè)務(wù)對(duì)象(Calculator)
通過(guò)AOP技術(shù),我們已經(jīng)將核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)完全分離,我們?cè)诙x業(yè)務(wù)對(duì)象時(shí),并不需要關(guān)注包括日志、性能監(jiān)測(cè)等方面,這也是AOP技術(shù)的優(yōu)勢(shì)。當(dāng)然,由于要利用.Net中的Attribute及代理技術(shù),對(duì)于施加了方面的業(yè)務(wù)對(duì)象而言,仍然需要一些小小的限制。
首先,我們應(yīng)該將定義好的方面Aspect施加給業(yè)務(wù)對(duì)象。其次,由于代理技術(shù)要獲取業(yè)務(wù)對(duì)象的上下文(Context),該上下文必須是指定的,而非默認(rèn)的上下文。上下文的獲得,是在業(yè)務(wù)對(duì)象創(chuàng)建和調(diào)用的時(shí)候,如果要獲取指定的上下文,在.Net中,要求業(yè)務(wù)對(duì)象必須繼承ContextBoundObject類(lèi)。
因此,最后業(yè)務(wù)對(duì)象Calculator類(lèi)的定義如下所示:
[MonitorAOP]
[LogAOP]
public class Calculator : ContextBoundObject
{
public int Add(int x,int y)
{
return x + y;
}
public int Substract(int x,int y)
{
return x - y;
}
}
[MonitorAOP]和[LogAOP]正是之前定義的方面Attribute,此外Calculator類(lèi)繼承了ContextBoundObject。除此之外,Calculator類(lèi)的定義與普通的對(duì)象定義無(wú)異。然而,正是利用AOP技術(shù),就可以攔截Calculator類(lèi)的Add()和Substract()方法,對(duì)其進(jìn)行日志記錄和性能監(jiān)測(cè)。而實(shí)現(xiàn)日志記錄和性能監(jiān)測(cè)的邏輯代碼,則完全與Calculator類(lèi)的Add()和Substract()方法分開(kāi),實(shí)現(xiàn)了兩者之間依賴(lài)的解除,有利于模塊的重用和擴(kuò)展。
應(yīng)用程序(Program)
我們可以實(shí)現(xiàn)簡(jiǎn)單的應(yīng)用程序,來(lái)看看業(yè)務(wù)對(duì)象Calculator施加了日志方面和性能檢測(cè)方面的效果:
class Program
{
[STAThread]
static void Main(string[] args)
{
Calculator cal = new Calculator();
cal.Add(3,5);
cal.Substract(3,5);
Console.ReadLine();
}
}
程序創(chuàng)建了一個(gè)Calculator對(duì)象,同時(shí)調(diào)用了Add()和Substract()方法。由于Calculator對(duì)象被施加了日志方面和性能檢測(cè)方面,因此運(yùn)行結(jié)果會(huì)將方法調(diào)用的詳細(xì)信息和調(diào)用前后的運(yùn)行當(dāng)前時(shí)間打印出來(lái)。
如果要改變記錄日志和性能監(jiān)測(cè)結(jié)果的方式,例如將其寫(xiě)到文件中,則只需要改變LogAdvice和MonitorAdvice的實(shí)現(xiàn),對(duì)于Calculator對(duì)象而言,則不需要作任何改變。
在《在.Net中關(guān)于A(yíng)OP的實(shí)現(xiàn)》我通過(guò)動(dòng)態(tài)代理的技術(shù),基本上實(shí)現(xiàn)了AOP的幾個(gè)技術(shù)要素,包括aspect,advice,pointcut。在文末我提到采用配置文件方式,來(lái)獲取advice和pointcut之間的映射,從而使得構(gòu)建aspect具有擴(kuò)展性。
細(xì)細(xì)思考這個(gè)問(wèn)題,我發(fā)現(xiàn)使用delegate來(lái)構(gòu)建advice,似乎并非一個(gè)明智的選擇。我在建立映射關(guān)系時(shí),是將要攔截的方法名和攔截需要實(shí)現(xiàn)的aspect邏輯建立一個(gè)對(duì)應(yīng)關(guān)系,而該aspect邏輯確實(shí)可以通過(guò)delegate,使其指向一族方法簽名與該委托完全匹配的方法。這使得advice能夠抽象化,以便于具體實(shí)現(xiàn)的擴(kuò)展。然而,委托其實(shí)現(xiàn)畢竟是面向過(guò)程的范疇,雖然在.Net下,delegate本身仍是一個(gè)類(lèi)對(duì)象,然而在創(chuàng)建具體的委托實(shí)例時(shí),仍然很難通過(guò)配置文件和反射技術(shù)來(lái)獲得。
考慮到委托具有的接口抽象的本質(zhì),也許采用接口的方式來(lái)取代委托更為可行。在之前的實(shí)現(xiàn)方案中,我為advice定義了兩個(gè)委托:
public delegate void BeforeAOPHandle(IMethodCallMessage callMsg);
public delegate void AfterAOPHandle(IMethodReturnMessage replyMsg);
我可以定義兩個(gè)接口IBeforeAction和IAfterAction,分別與這兩個(gè)委托相對(duì)應(yīng):
public interface IBeforeAdvice
{
void BeforeAdvice(IMethodCallMessage callMsg);
}
public interface IAfterAdvice
{
void AfterAdvice(IMethodReturnMessage returnMsg);
}
通過(guò)定義的接口,可以將Advice與Aspect分離開(kāi)來(lái),這也完全符合OO思想中的“責(zé)任分離”原則。
(注:為什么要為Advice定義兩個(gè)接口?這是考慮到有些Aspect只需要提供Before或After兩個(gè)邏輯之一,如權(quán)限控制,就只需要before Action。)
那么當(dāng)類(lèi)庫(kù)使用者,要定義自己的Aspect時(shí),就可以定義具體的Advice類(lèi),來(lái)實(shí)現(xiàn)這兩個(gè)接口,以及具體的Advice邏輯了。例如,之前提到的日志Aspect:
public class LogAdvice:IAfterAdvice,IBeforeAdvice
{
#region IBeforeAdvice Members
public void BeforeAdvice(IMethodCallMessage callMsg)
{
if (callMsg == null)
{
return;
}
Console.WriteLine("{0}({1},{2})",
callMsg.MethodName, callMsg.GetArg(0),
callMsg.GetArg(1));
}
#endregion
#region IAfterAdvice Members
public void AfterAdvice(IMethodReturnMessage returnMsg)
{
if (returnMsg == null)
{
return;
}
Console.WriteLine("Result is {0}", returnMsg.ReturnValue);
}
#endregion
}
而在A(yíng)OPSink類(lèi)的派生類(lèi)中,添加方法名與Advice映射關(guān)系(此映射關(guān)系,我們即可理解為AOP的pointcut)時(shí),就可以添加實(shí)現(xiàn)了Advice接口的類(lèi)對(duì)象,如:
public override void AddAllBeforeAdvices()
{
AddBeforeAdvice("ADD",new LogAdvice());
AddBeforeAdvice("SUBSTRACT", new LogAdvice());
}
public override void AddAllAfterAdvices()
{
AddAfterAdvice("ADD",new LogAdvice());
AddAfterAdvice("SUBSTRACT", new LogAdvice());
}
由于LogAdvice類(lèi)實(shí)現(xiàn)了接口IBeforeAdvice和IAfterAdvice,因此諸如new LogAdvice的操作均可以通過(guò)反射來(lái)創(chuàng)建該實(shí)例,如:
IBeforeAdvice beforeAdvice =
(IBeforeAdvice)Activator.CreateInstance("Wayfarer.AOPSample","Wayfarer.AOPSample.LogAdvice").Unwrap();
而CreateInstance()方法的參數(shù)值,是完全可以通過(guò)配置文件來(lái)配置的:
<aop>
<aspect value ="LOG">
<advice type="before" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">
<pointcut>ADDpointcut>
<pointcut>SUBSTRACTpointcut>
advice>
<advice type="after" assembly="Wayfarer.AOPSample" class="Wayfarer.AOPSample.LogAdvice">
<pointcut>ADDpointcut>
<pointcut>SUBSTRACTpointcut>
advice>
aspect>
aop>
這無(wú)疑改善了AOP實(shí)現(xiàn)的擴(kuò)展性。
《在.Net中關(guān)于A(yíng)OP的實(shí)現(xiàn)》實(shí)現(xiàn)AOP的方案,要求包含被攔截方法的類(lèi)必須繼承ContextBoundObject。這是一個(gè)比較大的限制。不僅如此,ContextBoundObject對(duì)程序的性能也有極大的影響。我們可以做一個(gè)小測(cè)試。定義兩個(gè)類(lèi),其中一個(gè)類(lèi)繼承ContextBoundObject。它們都實(shí)現(xiàn)了一個(gè)累加的操作:
class NormalObject
{
public void Sum(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
Console.WriteLine("The result is {0}",sum);
Thread.Sleep(10);
}
}
class MarshalObject:ContextBoundObject
{
public void Sum(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
Console.WriteLine("The result is {0}", sum);
Thread.Sleep(10);
}
}
然后執(zhí)行這兩個(gè)類(lèi)的Sum()方法,測(cè)試其性能:
class Program
{
static void Main(string[] args)
{
long normalObjMs, marshalObjMs;
Stopwatch watch = new Stopwatch();
NormalObject no = new NormalObject();
MarshalObject mo = new MarshalObject();
watch.Start();
no.Sum(1000000);
watch.Stop();
normalObjMs = watch.ElapsedMilliseconds;
watch.Reset();
watch.Start();
mo.Sum(1000000);
watch.Stop();
marshalObjMs = watch.ElapsedMilliseconds;
watch.Reset();
Console.WriteLine("The normal object consume
{0} milliseconds.",normalObjMs);
Console.WriteLine("The contextbound object consume {0} milliseconds.",marshalObjMs);
Console.ReadLine();
}
}
得到的結(jié)果如下:
從性能的差異看,兩者之間的差距是比較大的。如果將其應(yīng)用在企業(yè)級(jí)的復(fù)雜邏輯上,這種區(qū)別就非常明顯了,對(duì)系統(tǒng)帶來(lái)的影響也是非常巨大的。
另外,在《在.Net中關(guān)于A(yíng)OP的實(shí)現(xiàn)》文章后,有朋友發(fā)表了很多中肯的意見(jiàn)。其中有人提到了AOPAttribute繼承ContextAttribute的問(wèn)題。評(píng)論中提及微軟在以后的版本中,不再提供ContextAttribute。如果真是如此,確有必要放棄繼承ContextAttribute的形式。不過(guò),在.Net中,除了ContextAttribute之外,還提供有一個(gè)接口IContextAttribute,該接口的定義為:
public interface IContextAttribute
{
void GetPropertiesForNewContext(IConstructionCallMessage msg);
bool IsContextOK(Context ctx, IConstructionCallMessage msg);
}
此時(shí)只需要將原來(lái)的AOPAttribute實(shí)現(xiàn)該接口即可:
public abstract class AOPAttribute:Attribute,
IContextAttribute//ContextAttribute
{
#region IContextAttribute Members
public void GetPropertiesForNewContext
(IConstructionCallMessage ctorMsg)
{
AOPProperty property = GetAOPProperty();
property.AspectXml = m_AspectXml;
property.AspectXmlFlag = m_AspectXmlFlag;
ctorMsg.ContextProperties.Add(property);
}
public bool IsContextOK(Context ctx,
IConstructionCallMessage ctorMsg)
{
return false;
}
#endregion
}
不知道,IContextAttribute似乎也會(huì)在未來(lái)的版本中被取消呢?
然而,從總體來(lái)看,這種使用ContextBoundObject的方式是不太理想的,也許它只能停留在實(shí)驗(yàn)室階段,或許期待微軟在未來(lái)的版本中得到更好的解決!
當(dāng)然,如果采用Castle的DynamicProxy技術(shù),可以突破必須繼承CotextBoundObject的局限,但隨著而來(lái)的局限卻是AOP攔截的方法,要求必須是virtual的。坦白說(shuō),這樣的限制,不過(guò)與前者乃“五十步笑百步”的區(qū)別而已。我還是期待有更好的解決方案。
說(shuō)到AOP的幾大要素,在這里可以補(bǔ)充說(shuō)說(shuō),它主要包括:
1、Cross-cutting concern
在OO模型中,雖然大部份的類(lèi)只有單一的、特定的功能,但它們通常會(huì)與其他類(lèi)有著共同的第二需求。例如,當(dāng)線(xiàn)程進(jìn)入或離開(kāi)某個(gè)方法時(shí),我們可能既要在數(shù)據(jù)訪(fǎng)問(wèn)層的類(lèi)中記錄日志,又要在UI層的類(lèi)中記錄日志。雖然每個(gè)類(lèi)的基本功能極然不同,但用來(lái)滿(mǎn)足第二需求的代碼卻基本相同。
2、Advice
它是指想要應(yīng)用到現(xiàn)有模型的附加代碼。例如在《在.Net中關(guān)于A(yíng)OP的實(shí)現(xiàn)》的例子中,是指關(guān)于打印日志的邏輯代碼。
3、Point-cut
這個(gè)術(shù)語(yǔ)是指應(yīng)用程序中的一個(gè)執(zhí)行點(diǎn),在這個(gè)執(zhí)行點(diǎn)上需要采用前面的cross-cutting concern。如例子中,執(zhí)行Add()方法時(shí)出現(xiàn)一個(gè)Point-cut,當(dāng)方法執(zhí)行完畢,離開(kāi)方法時(shí)又出現(xiàn)另一個(gè)Point-cut。
4、Aspect
Point-cut和advice結(jié)合在一起就叫做aspect。如例子中的Log和Monitor。在對(duì)本例的重構(gòu)中,我已經(jīng)AOPSink更名為Aspect,相應(yīng)的LogAOPSink、MonitorAOPSink也更名為L(zhǎng)ogAspect,MonitorAspect。
以上提到的PointCut和Advice在A(yíng)OP技術(shù)中,通常稱(chēng)為動(dòng)態(tài)橫切技術(shù)。與之相對(duì)應(yīng)的,是較少被提及的靜態(tài)橫切。它與動(dòng)態(tài)橫切的區(qū)別在于它并不修改一個(gè)給定對(duì)象的執(zhí)行行為,相反,它允許通過(guò)引入附加的方法屬性和字段來(lái)修改對(duì)象固有的結(jié)構(gòu)。在很多AOP實(shí)現(xiàn)中,將靜態(tài)橫切稱(chēng)為introduce或者mixin。
在開(kāi)發(fā)應(yīng)用系統(tǒng)時(shí),如果需要在不修改原有代碼的前提下,引入第三方產(chǎn)品和API庫(kù),靜態(tài)橫切技術(shù)是有很大的用武之地的。從這一點(diǎn)來(lái)看,它有點(diǎn)類(lèi)似于設(shè)計(jì)模式中提到的Adapter模式需要達(dá)到的目標(biāo)。不過(guò),看起來(lái)靜態(tài)橫切技術(shù)應(yīng)比Adapter模式更加靈活和功能強(qiáng)大。
例如,一個(gè)已經(jīng)實(shí)現(xiàn)了收發(fā)郵件的類(lèi)Mail。然而它并沒(méi)有實(shí)現(xiàn)地址驗(yàn)證的功能。現(xiàn)在第三方提供了驗(yàn)證功能的接口IValidatable:
public interface IValidatable
{
bool ValidateAddress();
}
如果沒(méi)有AOP,采用設(shè)計(jì)模式的方式,在不改變Mail類(lèi)的前提下,可以通過(guò)Adapter模式,引入MailAdater,繼承Mail類(lèi),同時(shí)實(shí)現(xiàn)IValidatable接口。采用introduce技術(shù),卻更容易實(shí)現(xiàn)該功能的擴(kuò)展,我們只需要定義aspect:(注:java代碼,使用了AspectJ)
import com.acme.validate.Validatable;
public aspect EmailValidateAspect
{
declare parents: Email implements IValidatable;
public boolean Email.validateAddress(){
if(this.getToAddress() != null){
return true;
}else{
return false;
}
}
}
從上可以看到,通過(guò)EmailValidateAspect方面,為Email類(lèi)introduce了新的方法ValidateAddress()。非常容易的就完成了Email的擴(kuò)展。
我們可以比較一下,如果采用Adapter模式,原有的Email類(lèi)是不能被顯示轉(zhuǎn)換為IValidatable接口的,也即是說(shuō)如下的代碼是不可行的:
Email mail = new Email();
IValidatable validate = ((IValidatable)mail).ValidateAddress();
要調(diào)用ValidateAddress()方法,必須通過(guò)EmailAdapter類(lèi)。然而通過(guò)靜態(tài)橫切技術(shù),上面的代碼就完全可行了。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載