轻量级AOP的另一种实现(100%开源)
此文继上篇《一个轻量级AOP的实现(开源)》。
准备出一个AOP的系列,目前正在构思中。
这一篇是从WebSharp这个很老的项目中抽出来的,实现方式和原理都很简单,比上一篇更适合于初步理解代理式AOP的原理,在这一篇弄完之后,会继续将Spring.NET的AOP实现方式抽出来,也做一个轻量级的。
说到轻量级,本人先发声明,本人不对那些大型或重型的框架,有任何敌对或排斥,只是更喜欢轻量的框架,使用起来清爽,不需要太多的配置。就像工作流之列的东西,本人就见过一个非常好的框架,你可以这样声明一个包含驳回,且一人通过全局通过的简单工作流。
WorkFlow.Create("title").Next("username",isSingleness).Next("username1").Submit();
//新建一个以title命名的工作流,由两个用户审批(username和username1),且使用独裁模式(Singleness,XX特色啊),然后提交这个工作流
很简单啊!也很轻量级,就一个dll,不到100k的大小,很爽。(内部开发,本人无权分发,请勿索取啊,(*^__^*) 嘻嘻)
非常喜欢这样的东西,所以在工作的时候,或多或少的都在找轻量级的东西,或者找重量级的往下扒,嘿嘿!如果您也有,共享一下哈!
当然有很多东西,轻量级的框架无法和重量级的框架相比,但是轻量级的框架却是理解这些大型框架的基础(我就不信Spring.NET一出来就是现在这么大型,软件也有其成长的过程)。
好了,废话就不说了!
由于本人有大概5套不一样的轻量级的AOP(重量级的一个都米有),实现的思路也不一样,这个大概是1年前的版本,非常经典的使用代理方式的AOP。
开始这个源自Websharp的AOP吧!先看系统的组成吧!如图:
一般AOP有三中实现方式:代理,织入,继承ContextBoundObject。(更全的讲解请看,AOP in .NET和现有AOP解决方案收集)
此AOP就是使用代理方式来实现的,所以在Aop.Proxy命名空间下。就5个代码文件,一个公开接口(IAspectAdvice),两个内部类(AspectManager和AspectProxy),一个无任何抽象方法的抽象类(AspectOrientedObject),一个特性类(AspectOrientedAttribute),外部可见对象三个:一个接口,一个抽象类和一个特性类。
先看接口:
using System.Runtime.Remoting.Messaging;
namespace Monk.Aop.Proxy
{
///<summary>
///切面通知接口
///</summary>
public interface IAspectAdvice
{
///<summary>
/// 方法执行前要进行的通知
///</summary>
///<param name="msg">要执行方法的调用消息</param>
void BeforeExecute(IMethodCallMessage msg);
///<summary>
/// 方法执行完进行的通知
///</summary>
///<param name="msg">要执行方法的调用消息</param>
void AfterExecute(IMethodReturnMessage msg);
}
}
只有两个方法,用来定义方法通知,一个是前通知(定义方法执行前要执行的通知函数),一个是尾通知(定义方法执行后要执行的通知函数)。
这一次就没有示例代码了,哈哈,自己试着写吧,很简单的。
来看那个传说中的无任何抽象方法的抽象类:
///<summary>
/// 切面对象基类
///</summary>
public abstract class AspectOrientedObject : ContextBoundObject{}
如果您看过上一篇,一定会觉得眼熟啊,没错,就是一样的!上一次没有讲为什么这样写,这一次补上吧!
这个抽象类本身没有任何方法,而它的父类ContextBoundObject也是没有没有任何方法的抽象类:
public abstract class ContextBoundObject : MarshalByRefObject{}
纳尼???
为什么会这样呢?看ContextBoundObject这个明晰命名吧,叫做Context-Bound-Object,中文可译为“上下文绑定对象”,Msdn上解释为“定义所有上下文绑定类的基类”,可是这个抽象类没有任何其他方法啊,为什么必须继承自这个类?再往上,穿越到AspectOrientedObject的父父类,ContextBoundObject的父类MarshalByRefObject,这也是一个抽象类,但是它是有很多的方法的,但都不是抽象的。
一般的抽象类声明出来要么是需要继承重写方法的,要么是没有抽象方法但是不能被实例化的。MarshalByRefObject没有任何一个抽象方法(代码就不贴了,有兴趣的自己拿ILSpy或.NET Reflector开源),不属于前者,那么就属于后者了,为什么不能实例化这样一个对象呢?
来看MarshalByRefObject的官方解释:
允许在支持远程处理的应用程序中跨应用程序域边界访问对象。
这是个神马解释?不理解啊,还好,还有下面的备注,Look:
应用程序域是一个操作系统进程中一个或多个应用程序所驻留的分区。同一应用程序域中的对象直接通信。不同应用程序域中的对象的通信方式有两种:一种是跨应用程序域边界传输对象副本,一种是使用代理交换消息。
MarshalByRefObject 是通过使用代理交换消息来跨应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象根据值隐式封送。当远程应用程序引用根据值封送的对象时,将跨应用程序域边界传递该对象的副本。
MarshalByRefObject 对象在本地应用程序域的边界内可直接访问。远程应用程序域中的应用程序首次访问 MarshalByRefObject 时,会向该远程应用程序传递代理。对该代理后面的调用将封送回驻留在本地应用程序域中的对象。
当跨应用程序域边界使用类型时,类型必须是从 MarshalByRefObject 继承的,而且由于对象的成员在创建它们的应用程序域之外无法使用,所以不得复制对象的状态。
怎么,好像,还是,有,那么,一点点的,让人不解嘞!(dudu的一篇文章不错,《JGTM'2004 [MVP] 对MarshalByRefObject的讲解》)
其实是这样的!
为了得到这个世界上最美的女人--海伦,希腊人决定和特洛伊人干一仗,这一仗一干就是九年多(能跟希腊人搞这么久,特洛伊人好生猛啊)。希腊人觉得这么搞下去不是办法呀,那个城的防守实在是牛X,只能使用“城内”外通“城外”的战略方针了(后此方针传到中国,演化为“农村”包围“城市”的伟大战略方针)。但是,怎么进城,搞了九年多都没进去,看来得想个比较具有创新价值的招数了,伟大的诺基亚曾说“科技以换壳为本”,人是进不去的,东西未必啊,找个神马东西把人包装一下,东西进去了,人也就能进去了,办法不错。造个大壳,装十几号人,抬到城外海滩,走,等那些SB把这玩意当宝贝来抬回去,晚上使用战略方针搞死他。。。。
这就是最简单的理解了,有两个应用程序域(希腊人域和特洛伊人域),无法直接通信(直接送军队进城),怎么办?把东西包装一下!计算机一个对象需要到另一个环境中时,要将其包装(marshal)成一个可以传输的形态(木马就能传啊,我传到海滩,你传回去呀),然后传输到目标地点后,打开包装还原成内存对象。你拥有这个对象的引用,虽然你可以调用它的方法,但实际上这些操作都是发生在远程的(我有木马,但这东西始终还是别人的)。而这样可以包装,传输,打开包装的对象就是MarshalByRefObject,它提供了这样一系列的方法。(几千年前的希腊人都懂跨应用程序域访问,不简单啊)
所以简单而言,MarshalByRefObject是这样一种对象:继承此类的对象可以跨越应用程序域边界被引用,甚至被远程引用。远程调用时,将产生一个远程对象在本地的透明代理,通过此代理来进行远程调用。(代理的问题后面有论述)
这就解释了,为什么不能被实例化。世界上没有任何一种包能真正的可以包装一切,那怎么办?我只告诉你,我有包,能包东西,能把包里面的东西取出来,至于什么样的东西,看过之后我再造个包就行了。你不能不给我包这样一种很抽象的东西,但是我知道装钱的时候拿钱包,装书的时候拿书包,装菜的时候拿菜篮子就行了!
好了,这个AspectOrientedObject抽象类就到这了,讲的可能不是能满足所有人,但是请不要喷我啊,好歹俺也写了这么多啊,o(∩_∩)o 哈哈!
下来是特性类:
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Permissions;
using System.Runtime.Remoting.Proxies;
namespace Monk.Aop.Proxy
{
[AttributeUsage(AttributeTargets.Class)]
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public sealed class AspectOrientedAttribute : ProxyAttribute
{
public AspectOrientedAttribute(){}
public override MarshalByRefObject CreateInstance(Type serverType)
{
MarshalByRefObject mbro = base.CreateInstance(serverType);
RealProxy realproxy = new AspectProxy(serverType, mbro);
return realproxy.GetTransparentProxy() as MarshalByRefObject;
}
}
}
这个特性类的实现很简单,只要标记了这个特性的对象,都能使用自定义代理,在ProxyAttribute的方法中,重写CreateInstance方法,使自定义的代理AspectProxy来创建对象。
为什么要这样做,因为要实现AOP就必须能拦截对象的方法。普通的对象其创建,删除都是有系统来操作的,我们无法知道什么时候这个对象执行了什么方法。而借用了代理的对象,你操作的对象其实只是这个对象的代理,无论执行方法去创建,还是获得什么属性,进行什么操作,都会交由代理去执行。我们能知道代理什么时候去执行方法,所以我们就能变相的去控制这个对象执行方法的过程。
代理在.Net中被分为透明代理(Transparent Proxy)和真实代理(Real Proxy)。Transparent Proxy和被其代理的对象完全没有任何区别,只有通过 RemotingServices.IsTransparentProxy 才能区分两者的区别。Real Proxy则是提供给 CLR 使用者扩展代理机制的切入点,通过从Real Proxy继承并实现 Invoke 方法,用户自定义代理实现可以自由的处理已经被从栈调用转换为消息调用的目标对象方法调用,如实现缓存、身份验证、安全检测、延迟加载等等。
如果我们希望自己定义的代理类能够“模仿”真实对象的能力,首先就需要实现透明代理。然而,CLR中虽然提供了这样一个透明代理类(_TransparentProxy),我们却不能让自己的代理类从透明代理类派生,也不能通过自定义Attribute、实现标志性接口等方式将代理类标识为透明代理,从而让CLR能够认识。要获取透明代理,必须要提供一个真实代理。一个真实代理是一个从System.Runtime.Remoting.Proxies.RealProxy派生而来的类。
using System;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Services;
namespace Monk.Aop.Proxy
{
internal class AspectProxy : RealProxy
{
///<summary>
/// 被代理的对象
///</summary>
private MarshalByRefObject target;
///<summary>
/// 构造函数
///</summary>
///<param name="myType">被代理的类的类型</param>
///<param name="obj">被代理的对象</param>
public AspectProxy(Type myType, MarshalByRefObject obj): base(myType)
{
target = obj;
}
///<summary>
/// 当在派生类中重写时,在当前实例所表示的远程对象上调用在所提供的 IMessage 中指定的方法
/// 在这里执行对方法执行的拦截处理
///</summary>
///<param name="msg">IMessage,包含有关方法调用的信息。</param>
///<returns>调用的方法所返回的消息,包含返回值和所有 out 或 ref 参数。</returns>
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage call = msg as IMethodCallMessage;
//通过AspectManager获得这个方法的所有拦截方法
IAspectAdvice[] myAspect = AspectManager.FindAspect(this.GetProxiedType(),
msg as IMethodMessage);
IMessage retMsg;
if (myAspect != null)
{
foreach (IAspectAdvice advice in myAspect)
{
//执行前通知方法
advice.BeforeExecute(call);
}
}
//构造对象
if (msg is IConstructionCallMessage)
{
IConstructionCallMessage ccm = (IConstructionCallMessage)msg;
RemotingServices.GetRealProxy(target).InitializeServerObject(ccm);
ObjRef oRef = RemotingServices.Marshal(target);
RemotingServices.Unmarshal(oRef);
retMsg = EnterpriseServicesHelper.CreateConstructionReturnMessage(ccm,
(MarshalByRefObject)this.GetTransparentProxy());
}
else
{
//一般方法
IMethodCallMessage mcm = (IMethodCallMessage)msg;
retMsg = RemotingServices.ExecuteMessage(target, mcm);
}
if (myAspect != null)
{
foreach (IAspectAdvice advice in myAspect)
{
//尾通知方法
advice.AfterExecute(retMsg as IMethodReturnMessage);
}
}
return retMsg;
}
}
}
下来将AspectManager和配置文件贴出来。
配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<Monk>
<Aop>
<ProxyAop>
<!--type是指要拦截的对象类型,providertype是指提供拦截方法的类型,
两者个格式是"assembly,classname" methodname指要拦截的方法名 -->
<Aspect type="Test.exe,Test.calculate" providertype="Test.exe,Test.Aspect" methodname="Add"/>
<Aspect type="TestFrm.exe,TestFrm.calculate" providertype="TestFrm.exe,TestFrm.Aspect" methodname="Add"/>
</ProxyAop>
</Aop>
</Monk>
AspectManager:
using System;
using System.Collections.Generic;
using System.Xml;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
namespace Monk.Aop.Proxy
{
internal static class AspectManager
{
static AspectManager()
{
try
{
XmlDocument doc = new XmlDocument();
doc.Load("Monk.xml");
XmlNodeList list = doc.SelectNodes("/Monk/Aop/ProxyAop/Aspect");
foreach (XmlNode node in list)
{
try
{
string type = node.Attributes["type"].Value + "," + node.Attributes["methodname"].Value;
string providertype = node.Attributes["providertype"].Value;
int index = providertype.IndexOf(',');
IAspectAdvice advice = Assembly.LoadFrom(providertype.Substring(0,
index)).CreateInstance(providertype.Substring(index + 1)) as IAspectAdvice;
if (cache.ContainsKey(type))
{
cache[type].Add(advice);
}
else
{
cache.Add(type, new List<IAspectAdvice>() { advice});
}
}
catch { }
}
}
catch { }
}
//缓存
private static Dictionary<string, List<IAspectAdvice>> cache = new Dictionary<string, List<IAspectAdvice>>();
//获取通知
public static IAspectAdvice[] FindAspect(Type t, IMethodMessage msg)
{
if (msg == null)
{
return null;
}
string s = t.Assembly.ManifestModule.Name + "," + t.FullName + "," + msg.MethodName;
if (cache.ContainsKey(s))
{
return cache[s].ToArray();
}
return null;
}
}
}
配置文件和AspectManager就不讲了,就是读取配置文件,然后利用反射实例化对象。
总结一下吧:
其实就是这样的,利用对象的代理来对对象进行操作,然后我们来操作这个代理,从而达到操作对象的目的。