在.NET 中实现 AOP
软件工程中的大多数思想都是集中在管理复杂度上面的--
结构化编程尝试通过粗粒度的代码与设计划分来降低复杂度;
面向对象编程尝试通过建立结合状态与行为的抽象体来降低复杂度;
基于组件的软件尝试通过基于抽象接口和协议划分应用程序模块来降低复杂度。
基于组件的软件的梦想是构造一个世界,让水平一般的程序员就能使用高级语言和工具将组件组装起来。这当然假设这个世界里面的问题域能被分解成相互之间通过简单的方法调用进行交互的各种离散组件。
基于组件的软件的前提却忽略了这样一个事实,一个程序的特定方面倾向于分散到一个应用的多个部分。安全性就是这样的一个方面,线程管理也是,并发控制也是,这个列表会很长。
不可避免地,一个应用趋于被众多处理那些不是问题域中心的程序方面的代码片断污染。通常,这些方面又倾向于跨问题域,因此需要可复用的解决方案。致力提供机制来解决这类问题的就是AOP(面向方面编程),一个在1997年先后被Gregor Kiczales,和Xeros PARC 提出来的术语。
CLR的AOP机制基本上就是将方法调用看作消息交换。
下面用代码说明这个机制:
using System;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Contexts;
using System.Threading;
using System.Diagnostics;
namespace MsgAsMethodCalls
{
public interface ICaculator
{
double Add(double x, double y);
double Multiply(double x, double y);
}
public class MyProxy : RealProxy
{
public MyProxy() : base(typeof(ICaculator)) { }
public override IMessage Invoke(IMessage msg)
{
if (msg as IMethodCallMessage != null)
{
IMessage response = Program.ProcessMessage(msg as IMethodCallMessage);
return response;
}
return null;
}
}
public class PriorityProxy : RealProxy
{
readonly MarshalByRefObject target;
readonly ThreadPriority level;
public PriorityProxy(MarshalByRefObject target,
Type type,
ThreadPriority level)
: base(type)
{
this.target = target;
this.level = level;
}
public override IMessage Invoke(IMessage request)
{
IMethodCallMessage call = (IMethodCallMessage)request;
Program.WireTap(call);
// step 1: adjust priority
Thread here = Thread.CurrentThread;
ThreadPriority old = here.Priority;
here.Priority = level;
// step 2: forward call
IMessage response = null;
IConstructionCallMessage ctor = call as IConstructionCallMessage;
if (ctor != null)
{
// we are holding a TP, so grab its RP
RealProxy defaultProxy = RemotingServices.GetRealProxy(target);
// as intermediatoe RP to invoke constructor
defaultProxy.InitializeServerObject(ctor);
// get OUR TP
MarshalByRefObject tp = (MarshalByRefObject)this.GetTransparentProxy();
// return a message containing our TP as the result of the constuctor call
response = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
}
else
{
response = RemotingServices.ExecuteMessage(target, call);
}
// step 3: restore old priority
here.Priority = old;
// step 4: return response message to TP
return response;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class PriorityProxyAttribute : ProxyAttribute
{
ThreadPriority level;
public PriorityProxyAttribute(ThreadPriority level)
{
this.level = level;
}
public override MarshalByRefObject CreateInstance(Type t)
{
// note that we delegate to our base to get an
// uninitialized instance!
MarshalByRefObject target = base.CreateInstance(t);
PriorityProxy rp = new PriorityProxy(target, t, level);
return (MarshalByRefObject)rp.GetTransparentProxy();
}
}
public class PriorityProperty : IContextProperty
{
ThreadPriority level;
public ThreadPriority Level { get { return level; } }
internal PriorityProperty(ThreadPriority level)
{ this.level = level; }
// IContextProperty members
public string Name { get { return "ThreadPriority"; } }
public bool IsNewContextOK(Context ctx)
{
return true;
}
public void Freeze(Context ctx) { }
}
public class PriorityAttribute : Attribute, IContextAttribute
{
ThreadPriority level;
internal PriorityAttribute(ThreadPriority level)
{ this.level = level; }
// IContextAttribute members
public bool IsContextOK(Context current,
IConstructionCallMessage ctor)
{
// Our property must be present!
object prop = current.GetProperty("ThreadPriority");
if (prop == null) return false;
// and its level must match the attribute's
PriorityProperty pp = (PriorityProperty)prop;
return pp.Level == this.level;
}
public void GetPropertiesForNewContext(
IConstructionCallMessage ctor)
{
// create new properties
IContextProperty prop = new PriorityProperty(this.level);
// associate property with constructor call
ctor.ContextProperties.Add(prop);
ctor.ContextProperties.Add(new TimingProperty());
ctor.ContextProperties.Add(new BoostProperty());
ctor.LogicalCallContext.SetData("clientpriority", new EnvoyData(Thread.CurrentThread.Priority));
}
}
public abstract class DefaultSink : IMessageSink
{
readonly protected IMessageSink next;
public DefaultSink(IMessageSink next)
{
this.next = next;
}
public IMessageSink NextSink { get { return next; } }
public virtual IMessage SyncProcessMessage(IMessage request)
{
IMessage response = next.SyncProcessMessage(request);
return response;
}
public virtual IMessageCtrl AsyncProcessMessage(IMessage request,
IMessageSink upcall)
{
IMessageCtrl ctrl = next.AsyncProcessMessage(request, upcall);
return ctrl;
}
}
public class ServerTimingSink : DefaultSink
{
public ServerTimingSink(IMessageSink next)
: base(next)
{ }
[ContextStatic]
static internal long totalTicks = 0;
public override IMessage SyncProcessMessage(IMessage r)
{
long start = DateTime.Now.Ticks;
IMessage p = next.SyncProcessMessage(r);
long end = DateTime.Now.Ticks;
totalTicks += end - start;
return p;
}
}
public class ClientTimingSink : DefaultSink
{
public ClientTimingSink(IMessageSink next)
: base(next)
{ }
public override IMessage SyncProcessMessage(IMessage r)
{
long start = DateTime.Now.Ticks;
IMessage p = next.SyncProcessMessage(r);
long end = DateTime.Now.Ticks;
ServerTimingSink.totalTicks += end - start;
return p;
}
}
public class TimingProperty
: IContextProperty,
IContributeClientContextSink,
IContributeServerContextSink
{
IContributeClientContextSink Members
IContributeServerContextSink Members
IContextProperty Members
}
internal class EnvoyData : ILogicalThreadAffinative
{
internal ThreadPriority clientPriority;
internal EnvoyData(ThreadPriority level)
{
this.clientPriority = level;
}
}
public class PriorityEnvoySink : DefaultSink
{
public PriorityEnvoySink(IMessageSink next) : base(next) { }
public override IMessage SyncProcessMessage(IMessage request)
{
IMethodCallMessage call = (IMethodCallMessage)request;
ThreadPriority p = Thread.CurrentThread.Priority;
EnvoyData ed = new EnvoyData(p);
call.LogicalCallContext.SetData("clientpriority", ed);
return next.SyncProcessMessage(call);
}
}
public class PriorityServerSink : DefaultSink
{
public PriorityServerSink(IMessageSink next) : base(next) { }
public override IMessage SyncProcessMessage(IMessage request)
{
// grab caller's priority from call context
IMethodCallMessage call = (IMethodCallMessage)request;
LogicalCallContext cc = call.LogicalCallContext;
EnvoyData ed = (EnvoyData)cc.GetData("clientpriority");
cc.FreeNamedDataSlot("clientpriority");
// proceed to boost-dispatch-unboost
Thread here = Thread.CurrentThread;
ThreadPriority old = here.Priority;
if (ed.clientPriority != ThreadPriority.Highest)
here.Priority = ed.clientPriority + 1;
IMessage resp = next.SyncProcessMessage(call);
if (ed.clientPriority != ThreadPriority.Highest)
here.Priority = old;
return resp;
}
}
public class BoostProperty :
IContextProperty,
IContributeServerContextSink,
IContributeEnvoySink
{
IContextProperty Members
IContributeServerContextSink Members
IContributeEnvoySink Members
}
public class MyCalc : ContextBoundObject, ICaculator
{
public static MyCalc Create(ThreadPriority level)
{
MyCalc target = new MyCalc();
PriorityProxy rp = new PriorityProxy(target, typeof(MyCalc), level);
return (MyCalc)rp.GetTransparentProxy();
}
private MyCalc() { }
public double Add(double x, double y) { return x + y; }
public double Multiply(double x, double y) { return x * y; }
}
[PriorityProxy(ThreadPriority.Highest)]
public class MyCalc3 : ContextBoundObject, ICaculator
{
public double Add(double x, double y) {
Console.WriteLine(Thread.CurrentThread.Priority);
return x + y;
}
public double Multiply(double x, double y) { return x * y; }
}
[Priority(ThreadPriority.Highest)]
public class MyCalc5 : ContextBoundObject, ICaculator
{
public double Add(double x, double y) {
// grab the object's context
Context here = Thread.CurrentContext;
// fecth the priority property
IContextProperty p = here.GetProperty("ThreadPriority");
PriorityProperty pp = (PriorityProperty)p;
Debug.Assert(pp.Level == ThreadPriority.Highest);
Console.WriteLine(Thread.CurrentThread.Priority);
return x + y;
}
public double Multiply(double x, double y) { return x * y; }
}
class Program
{
public static void WireTap(IMethodMessage msg){
IMethodCallMessage call = (IMethodCallMessage)msg;
Console.WriteLine("<{0}>",call.MethodName);
for(int i=0;i<call.ArgCount;++i)
Console.WriteLine("<{0}>{1}</{0}>",
call.GetArgName(i),
call.GetArg(i));
Console.WriteLine("</{0}>",call.MethodName);
}
public static IMethodReturnMessage ProcessMessage(IMethodCallMessage request)
{
switch (request.MethodName)
{
case "Add":
{
double x = (double)request.GetInArg(0);
double y = (double)request.GetInArg(1);
double result = x + y;
return new ReturnMessage(result, null, 0, null, request);
}
case "Multiply":
{
double x = (double)request.GetInArg(0);
double y = (double)request.GetInArg(1);
double result = x * y;
return new ReturnMessage(result, null, 0, null, request);
}
default:
{
string exm = string.Format("{0} not implemented",
request.MethodName);
Exception ex = new NotImplementedException(exm);
return new ReturnMessage(ex, request);
}
}
}
static void Main(string[] args)
{
// isinst
MyProxy rp = new MyProxy();
object tp = rp.GetTransparentProxy();
ICaculator calc = tp as ICaculator;
Debug.Assert(calc != null);
double result = calc.Add(3, 4);
Debug.Assert(result == 7);
// factory method
calc = MyCalc.Create(ThreadPriority.Highest);
result = calc.Add(3, 4);
Debug.Assert(result == 7);
// ProxyAttribute
calc = new MyCalc3();
result = calc.Add(3, 4);
Debug.Assert( result == 7);
// ContextProperty
calc = new MyCalc5();
result = calc.Add(3, 4);
Debug.Assert(result == 7);
}
}
}
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Contexts;
using System.Threading;
using System.Diagnostics;
namespace MsgAsMethodCalls
{
public interface ICaculator
{
double Add(double x, double y);
double Multiply(double x, double y);
}
public class MyProxy : RealProxy
{
public MyProxy() : base(typeof(ICaculator)) { }
public override IMessage Invoke(IMessage msg)
{
if (msg as IMethodCallMessage != null)
{
IMessage response = Program.ProcessMessage(msg as IMethodCallMessage);
return response;
}
return null;
}
}
public class PriorityProxy : RealProxy
{
readonly MarshalByRefObject target;
readonly ThreadPriority level;
public PriorityProxy(MarshalByRefObject target,
Type type,
ThreadPriority level)
: base(type)
{
this.target = target;
this.level = level;
}
public override IMessage Invoke(IMessage request)
{
IMethodCallMessage call = (IMethodCallMessage)request;
Program.WireTap(call);
// step 1: adjust priority
Thread here = Thread.CurrentThread;
ThreadPriority old = here.Priority;
here.Priority = level;
// step 2: forward call
IMessage response = null;
IConstructionCallMessage ctor = call as IConstructionCallMessage;
if (ctor != null)
{
// we are holding a TP, so grab its RP
RealProxy defaultProxy = RemotingServices.GetRealProxy(target);
// as intermediatoe RP to invoke constructor
defaultProxy.InitializeServerObject(ctor);
// get OUR TP
MarshalByRefObject tp = (MarshalByRefObject)this.GetTransparentProxy();
// return a message containing our TP as the result of the constuctor call
response = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
}
else
{
response = RemotingServices.ExecuteMessage(target, call);
}
// step 3: restore old priority
here.Priority = old;
// step 4: return response message to TP
return response;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class PriorityProxyAttribute : ProxyAttribute
{
ThreadPriority level;
public PriorityProxyAttribute(ThreadPriority level)
{
this.level = level;
}
public override MarshalByRefObject CreateInstance(Type t)
{
// note that we delegate to our base to get an
// uninitialized instance!
MarshalByRefObject target = base.CreateInstance(t);
PriorityProxy rp = new PriorityProxy(target, t, level);
return (MarshalByRefObject)rp.GetTransparentProxy();
}
}
public class PriorityProperty : IContextProperty
{
ThreadPriority level;
public ThreadPriority Level { get { return level; } }
internal PriorityProperty(ThreadPriority level)
{ this.level = level; }
// IContextProperty members
public string Name { get { return "ThreadPriority"; } }
public bool IsNewContextOK(Context ctx)
{
return true;
}
public void Freeze(Context ctx) { }
}
public class PriorityAttribute : Attribute, IContextAttribute
{
ThreadPriority level;
internal PriorityAttribute(ThreadPriority level)
{ this.level = level; }
// IContextAttribute members
public bool IsContextOK(Context current,
IConstructionCallMessage ctor)
{
// Our property must be present!
object prop = current.GetProperty("ThreadPriority");
if (prop == null) return false;
// and its level must match the attribute's
PriorityProperty pp = (PriorityProperty)prop;
return pp.Level == this.level;
}
public void GetPropertiesForNewContext(
IConstructionCallMessage ctor)
{
// create new properties
IContextProperty prop = new PriorityProperty(this.level);
// associate property with constructor call
ctor.ContextProperties.Add(prop);
ctor.ContextProperties.Add(new TimingProperty());
ctor.ContextProperties.Add(new BoostProperty());
ctor.LogicalCallContext.SetData("clientpriority", new EnvoyData(Thread.CurrentThread.Priority));
}
}
public abstract class DefaultSink : IMessageSink
{
readonly protected IMessageSink next;
public DefaultSink(IMessageSink next)
{
this.next = next;
}
public IMessageSink NextSink { get { return next; } }
public virtual IMessage SyncProcessMessage(IMessage request)
{
IMessage response = next.SyncProcessMessage(request);
return response;
}
public virtual IMessageCtrl AsyncProcessMessage(IMessage request,
IMessageSink upcall)
{
IMessageCtrl ctrl = next.AsyncProcessMessage(request, upcall);
return ctrl;
}
}
public class ServerTimingSink : DefaultSink
{
public ServerTimingSink(IMessageSink next)
: base(next)
{ }
[ContextStatic]
static internal long totalTicks = 0;
public override IMessage SyncProcessMessage(IMessage r)
{
long start = DateTime.Now.Ticks;
IMessage p = next.SyncProcessMessage(r);
long end = DateTime.Now.Ticks;
totalTicks += end - start;
return p;
}
}
public class ClientTimingSink : DefaultSink
{
public ClientTimingSink(IMessageSink next)
: base(next)
{ }
public override IMessage SyncProcessMessage(IMessage r)
{
long start = DateTime.Now.Ticks;
IMessage p = next.SyncProcessMessage(r);
long end = DateTime.Now.Ticks;
ServerTimingSink.totalTicks += end - start;
return p;
}
}
public class TimingProperty
: IContextProperty,
IContributeClientContextSink,
IContributeServerContextSink
{
IContributeClientContextSink Members
IContributeServerContextSink Members
IContextProperty Members
}
internal class EnvoyData : ILogicalThreadAffinative
{
internal ThreadPriority clientPriority;
internal EnvoyData(ThreadPriority level)
{
this.clientPriority = level;
}
}
public class PriorityEnvoySink : DefaultSink
{
public PriorityEnvoySink(IMessageSink next) : base(next) { }
public override IMessage SyncProcessMessage(IMessage request)
{
IMethodCallMessage call = (IMethodCallMessage)request;
ThreadPriority p = Thread.CurrentThread.Priority;
EnvoyData ed = new EnvoyData(p);
call.LogicalCallContext.SetData("clientpriority", ed);
return next.SyncProcessMessage(call);
}
}
public class PriorityServerSink : DefaultSink
{
public PriorityServerSink(IMessageSink next) : base(next) { }
public override IMessage SyncProcessMessage(IMessage request)
{
// grab caller's priority from call context
IMethodCallMessage call = (IMethodCallMessage)request;
LogicalCallContext cc = call.LogicalCallContext;
EnvoyData ed = (EnvoyData)cc.GetData("clientpriority");
cc.FreeNamedDataSlot("clientpriority");
// proceed to boost-dispatch-unboost
Thread here = Thread.CurrentThread;
ThreadPriority old = here.Priority;
if (ed.clientPriority != ThreadPriority.Highest)
here.Priority = ed.clientPriority + 1;
IMessage resp = next.SyncProcessMessage(call);
if (ed.clientPriority != ThreadPriority.Highest)
here.Priority = old;
return resp;
}
}
public class BoostProperty :
IContextProperty,
IContributeServerContextSink,
IContributeEnvoySink
{
IContextProperty Members
IContributeServerContextSink Members
IContributeEnvoySink Members
}
public class MyCalc : ContextBoundObject, ICaculator
{
public static MyCalc Create(ThreadPriority level)
{
MyCalc target = new MyCalc();
PriorityProxy rp = new PriorityProxy(target, typeof(MyCalc), level);
return (MyCalc)rp.GetTransparentProxy();
}
private MyCalc() { }
public double Add(double x, double y) { return x + y; }
public double Multiply(double x, double y) { return x * y; }
}
[PriorityProxy(ThreadPriority.Highest)]
public class MyCalc3 : ContextBoundObject, ICaculator
{
public double Add(double x, double y) {
Console.WriteLine(Thread.CurrentThread.Priority);
return x + y;
}
public double Multiply(double x, double y) { return x * y; }
}
[Priority(ThreadPriority.Highest)]
public class MyCalc5 : ContextBoundObject, ICaculator
{
public double Add(double x, double y) {
// grab the object's context
Context here = Thread.CurrentContext;
// fecth the priority property
IContextProperty p = here.GetProperty("ThreadPriority");
PriorityProperty pp = (PriorityProperty)p;
Debug.Assert(pp.Level == ThreadPriority.Highest);
Console.WriteLine(Thread.CurrentThread.Priority);
return x + y;
}
public double Multiply(double x, double y) { return x * y; }
}
class Program
{
public static void WireTap(IMethodMessage msg){
IMethodCallMessage call = (IMethodCallMessage)msg;
Console.WriteLine("<{0}>",call.MethodName);
for(int i=0;i<call.ArgCount;++i)
Console.WriteLine("<{0}>{1}</{0}>",
call.GetArgName(i),
call.GetArg(i));
Console.WriteLine("</{0}>",call.MethodName);
}
public static IMethodReturnMessage ProcessMessage(IMethodCallMessage request)
{
switch (request.MethodName)
{
case "Add":
{
double x = (double)request.GetInArg(0);
double y = (double)request.GetInArg(1);
double result = x + y;
return new ReturnMessage(result, null, 0, null, request);
}
case "Multiply":
{
double x = (double)request.GetInArg(0);
double y = (double)request.GetInArg(1);
double result = x * y;
return new ReturnMessage(result, null, 0, null, request);
}
default:
{
string exm = string.Format("{0} not implemented",
request.MethodName);
Exception ex = new NotImplementedException(exm);
return new ReturnMessage(ex, request);
}
}
}
static void Main(string[] args)
{
// isinst
MyProxy rp = new MyProxy();
object tp = rp.GetTransparentProxy();
ICaculator calc = tp as ICaculator;
Debug.Assert(calc != null);
double result = calc.Add(3, 4);
Debug.Assert(result == 7);
// factory method
calc = MyCalc.Create(ThreadPriority.Highest);
result = calc.Add(3, 4);
Debug.Assert(result == 7);
// ProxyAttribute
calc = new MyCalc3();
result = calc.Add(3, 4);
Debug.Assert( result == 7);
// ContextProperty
calc = new MyCalc5();
result = calc.Add(3, 4);
Debug.Assert(result == 7);
}
}
}