.net中实现AOP(二)
上篇文章我大概说了一下在.net中如何利用自定义代理类来实现AOP,那篇文章主要说了些原理,这篇我们直接上代码。
第一:AspectManagedAttribute特性。说到代理机制,那么它直接针对的应该是一个类而不是具体的方法,所以这里我们实现AOP的最小单位就是类,我们定义一个AspectManagedAttribute特性,它标识了具体类会受到AOP管理。
说明:
1:由于这个特性允许标识在类上,所在需要加上[AttributeUsage(AttributeTargets.Class)]。
2:AOP可以实现多种非业务逻辑的"横切点",这里我们给AspectManagedAttribute增加一个枚举AspectManagedType,这里分为两种:
1》:普通的异常捕获,当请求的方法抛出异常时,记录在日志中,同时返回空对象。
2》:在普通异常捕获的基础上,增加方法调用的时间记录。
{
ErrorHandle,
ElapsedTime
}
3》:继承ProxyAttribute:指示对象类型需要自定义代理。
4》:重写ProxyAttribute的方法:MarshalByRefObject CreateInstance(Type serverType)。根据不同的AspectManagedType创建不同的自定义代理类。
{
MarshalByRefObject mobj = base.CreateInstance(serverType);
if (aspectManaged)
{
RealProxy realProxy = this .GetCurrentRealProxy (this.aspectManagedType , serverType, mobj);
MarshalByRefObject retobj = realProxy.GetTransparentProxy() as MarshalByRefObject;
return retobj;
}
else
{
return mobj;
}
}
private RealProxy GetCurrentRealProxy(AspectManagedType _aspectManagedType, Type serverType, MarshalByRefObject mobj)
{
RealProxy real = null;
if (AspectsManager.AspectsProxy.ContainsKey(serverType.Name))
{
return AspectsManager.AspectsProxy[serverType.Name];
}
switch (_aspectManagedType)
{
case AspectManagedType .ElapsedTime :
real = new AspectProxyElapsedTime(serverType, mobj);
break;
case AspectManagedType.ErrorHandle :
real = new AspectProxyErrorLog(serverType, mobj);
break;
default :
real = new AspectProxyErrorLog(serverType, mobj);
break;
}
lock (AspectsManager.AspectsProxy)
{
AspectsManager.AspectsProxy.Add(serverType.Name, real);
}
return real;
}
5:把所有的CustomProxy放入静态变量中,有小小的性能提升。这里我们创建了AspectsManager,代码比较简单,只包含一个管理器容器。
{
public static Dictionary<string , RealProxy> AspectsProxy = new Dictionary<string , RealProxy>();
}
第二:自定义代理类的实现,这里我就贴异常处理的AspectProxyErrorLog:
说明:
1:需要继承RealProxy,它提供代理的基本功能。
2:重写RealProxy的IMessage Invoke(IMessage msg),对当前实例所表示的远程对象调用在所提供的 IMessage 中指定的方法。
/// 当在派生类中重写时,在当前实例所表示的远程对象上调用在所提供的 IMessage 中指定的方法。<br />
/// WebsharpAspect在这里执行对方法执行的拦截处理
/// </summary>
/// <param name="msg">IMessage,包含有关方法调用的信息。</param>
/// <returns>调用的方法所返回的消息,包含返回值和所有 out 或 ref 参数。</returns>
public override IMessage Invoke(IMessage msg)
{
IMessage retMsg=null ;
IMethodCallMessage methodCall = (IMethodCallMessage)msg;
IMethodReturnMessage methodReturn = null;
object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
methodCall.Args.CopyTo(copiedArgs, 0);
object[] attrs = null;
CoustomerErrorHandleAttribute ceha = null;
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;
attrs = methodCall.MethodBase.GetCustomAttributes(typeof(CoustomerErrorHandleAttribute), false);
ceha = LogManagerFactory.GetCoustomerErrorHandleAttribute(attrs, methodCall.MethodBase.Name );
if (null != ceha)
{
LogManage = ceha.ILogName;
}
try
{
object returnValue = methodCall.MethodBase.Invoke(this.target, copiedArgs);
methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
}
catch (Exception ex)
{
if (null != this.LogManage)
{
this.LogManage.Error(ceha.MethodErrorText + ex.ToString());
}
methodReturn = new ReturnMessage(null, methodCall);
}
retMsg = methodReturn;
}
return retMsg;
}
3:当捕获到异常后,首先将异常信息写入日志,为了让方法不对外抛出异常,我们可以对返回信息做处理,让它直接返回null。不知道这样做是否妥当,大家多多提意见啊。
第三:.net中要想实现方法的捕获,当然少不了ContextBoundObject:定义所有上下文绑定类的基类。这里我们可以创建一个空类,然后继承ContextBoundObject,客户端的类只需要做下继承就行。
{
}
第四:客户端调用。
1:
public class GetMemberInfoProxy : AspectObject
2:如果项目比较多,我们希望把日志能记录到不同的位置,往往我们会创建多个ILog接口,这里可以在方法上下手,创建一个方法级的CoustomerErrorHandle。主要提供如下几个属性:
1>:public ILog ILogName { get; set; } 提供自定义的日志接口。当然ILog不能体现在Atttribute中,我们可以采用枚举来替换,然后在后端来动态创建ILog接口。
2>:public string MethodElapsedTimeText { get; set; } 方法耗时的提示语。当此参数没有时,默认为方法名。
3>: public string MethodErrorText { get; set; } 方法出异常时的提示语。当此参数没有时,默认为方法名。
3:方法级的示例:
public MemberInfo GetMemberInfo(long cardNo)
总结:
本人对本篇文章的AOP做了下性能测试,没发现多大的消耗,大家对AOP性能有啥看法,多多指教。