【C# / Pattern】C#中利用Attribute实现AOP(面向切面编程)

AOP - 面向切面编程

在软件业,AOPAspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。[百度百科 - AOP]

OOP ? AOP ?

传统的OOP编程,每个类专注于实现各自的功能和职责,进行相对独立的封装,以继承的方式来实现功能结构的扩展。而AOP,则是尽可能将通用的功能逻辑,从不相关的类中分离,单独实现,如果这个逻辑发生变化,则不需要去修改所有使用这个逻辑的类,只需要修改这个通用的行为。
用OOP的方式,实现大多数业务功能逻辑的结构,都是很合适的,但是当一些功能需要横穿所有功能模块时,会导致耦合剧增,并不合适;而AOP则如他的名字一样,可以很好的解决横向切面的问题。AOP是一种只关注切面,专注提取通用的功能,而不关心具体业务逻辑的编程方法。

C# 中实现 AOP

按照AOP的思路,通用的功能要能横向的影响所有功能模块,比如所有的模块功能调用时都需要进行访问权限验证,比如很多新接口都需要加调试日志,如果对这些模块和接口一一作出修改,代价很大。C# 的 Attribute(类似Java的注解),可以对函数,类,属性做出标注,我们可以很简单的对某个方法加上一个 [Log],来表示这个函数在执行时需要记录日志。此时我们需要对这个函数的参数,返回值进行拦截,才能进行验证、日志记录等操作。那么,具体如何实现呢。

代码实现

  • AOPContextAttribute.cs
    定义一个AOP上下文特性,用于标注需要支持AOP方法的类,通过 ContextAttributeIContributeObjectSink 来获取类的上下文环境,这是通过 Attribute 拦截参数和获取返回值的前提。
	[AttributeUsage(AttributeTargets.Class)]
	public sealed class AOPContextAttribute : ContextAttribute, IContributeObjectSink
	{
		public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink) 
		{
			return new AOPHandler(nextSink);
		}

		public AOPContextAttribute() : base("AOPContext") 
		{
		}
	}
  • AOPContext.cs
    实现一个继承自 ContextBoundObject 的类,并标注 [AOPContext] 特性,两者配合,使得这个类下的方法可以被成功拦截。需要支持AOP的类,继承这个类即可。
	[AOPContext]
	public class AOPContext : ContextBoundObject 
	{

	}
  • AOPMethodAttribute.cs
    这个特性用于标注实现支持AOP的类中,需要被拦截的函数。
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class AOPMethodAttribute : Attribute
	{
	}
  • AOPHandler.cs
    这个类用于实现 具体的参数拦截和返回值获取操作。
	public class AOPHandler : IMessageSink 
	{
		/// <summary>
		/// 下一个接收器
		/// </summary>
		private readonly IMessageSink _nextSink;

		public AOPHandler(IMessageSink nextSink)
		{
			_nextSink = nextSink;
		}

		public IMessageSink NextSink
		{
			get { return _nextSink; }
		}

		/// <summary>
		/// 同步处理方法  
		/// </summary>
		/// <param name="msg"></param>
		/// <returns></returns>
		public IMessage SyncProcessMessage(IMessage msg)
		{
			IMessage message = null;
			var callMessage = msg as IMethodCallMessage;
			if (callMessage != null)
			{
				var attr = ReflectionUtil.GetAttribute<AOPMethodAttribute>(callMessage.MethodBase as MethodInfo);
				if (attr != null)
				{
					PreProceed(msg);
					message = _nextSink.SyncProcessMessage(msg);
					PostProceed(message);
				}
			}
			else
			{
				message = _nextSink.SyncProcessMessage(msg);
			}
			return message;
		}

		/// <summary>
		/// 异步处理方法
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="replySink"></param>
		/// <returns></returns>
		public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) 
		{
			return null;
		}

		/// <summary>
		/// 方法执行前
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="before"></param>
		public void PreProceed(IMessage msg) 
		{
			var message = msg as IMethodMessage;
			// 获取到的参数列表 object[]
			var paramss = message.Args;
		}

		/// <summary>
		/// 方法执行后
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="after"></param>
		public void PostProceed(IMessage msg) 
		{
			var message = msg as IMethodReturnMessage;
			// 获取到的返回值
			var param = message.ReturnValue;
		}
	}

改进

通过以上的代码,我们已经可以做到,自定义一个功能模块类,然后让其继承 AOPContext 类,并在类中某些方法上标注 [AOPMethod] ,就可以在改方法执行前,从 AOPHandler 中获取到这个方法的参数,并在方法之行结束后,获取到这个方法的返回值。
这只是一段测试代码,应用到具体业务,肯定需要编写很多诸如 AuthCheckAttribute AuthCheckHandler 来实现权限验证,LogAttribute LogHandler 来实现日志记录。每实现一个通用方法,不仅需要实现对应的逻辑,还需要额外添加很多类,比较麻烦。
尝试做出如下改进,使用特性标注出拦截到消息后的处理类和函数,并使用反射来执行处理函数。

  • 增加 AOPBeforeAttribute.cs
    用于标注需要拦截参数的方法,和指出对应的处理函数。
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class AOPBeforeAttribute : Attribute
	{
		public string FullClassName;
		public string StaticMethodName;

		public AOPBeforeAttribute(string fullClassName, string staticMethodName)
		{
			FullClassName = fullClassName;
			StaticMethodName = staticMethodName;
		}
	}
  • 增加 AOPAfterAttribute.cs
    用于标注需要获取返回值的方法,和指出对应的处理函数。
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class AOPAfterAttribute : Attribute 
	{
		public string FullClassName;
		public string StaticMethodName;

		public AOPAfterAttribute(string fullClassName, string staticMethodName) 
		{
			FullClassName = fullClassName;
			StaticMethodName = staticMethodName;
		}
	}
  • 修改 AOPHandler.cs
	public class AOPHandler : IMessageSink
	{
		/// <summary>
		/// 下一个接收器
		/// </summary>
		private readonly IMessageSink _nextSink;

		public AOPHandler(IMessageSink nextSink)
		{
			_nextSink = nextSink;
		}

		public IMessageSink NextSink
		{
			get { return _nextSink; }
		}

		/// <summary>
		/// 同步处理方法  
		/// </summary>
		/// <param name="msg"></param>
		/// <returns></returns>
		public IMessage SyncProcessMessage(IMessage msg) 
		{
			IMessage message = null;
			var callMessage = msg as IMethodCallMessage;
			if (callMessage != null)
			{
				// Before
				var before = ReflectionUtil.GetAttribute<AOPBeforeAttribute>(callMessage.MethodBase as MethodInfo);
				if (before != null)
				{
					PreProceed(msg, before);
				}
				// Invoke
				message = _nextSink.SyncProcessMessage(msg);
				// After
				var after = ReflectionUtil.GetAttribute<AOPAfterAttribute>(callMessage.MethodBase as MethodInfo);
				if (after != null)
				{
					PostProceed(message, after);
				}
			}
			else
			{
				message = _nextSink.SyncProcessMessage(msg);
			}		
			return message;
		}

		/// <summary>
		/// 异步处理方法
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="replySink"></param>
		/// <returns></returns>
		public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
		{
			return null;
		}

		/// <summary>
		/// 方法执行前
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="before"></param>
		public void PreProceed(IMessage msg, AOPBeforeAttribute before) 
		{
			var message = msg as IMethodMessage;
			var type = Assembly.GetCallingAssembly().GetType(before.FullClassName);
			var param = message.Args;
			type.InvokeMember(before.StaticMethodName, BindingFlags.InvokeMethod, null, null, param);
		}

		/// <summary>
		/// 方法执行后
		/// </summary>
		/// <param name="msg"></param>
		/// <param name="after"></param>
		public void PostProceed(IMessage msg, AOPAfterAttribute after) 
		{
			var message = msg as IMethodReturnMessage;
			var type = Assembly.GetCallingAssembly().GetType(after.FullClassName);
			var param = message.ReturnValue;
			type.InvokeMember(after.StaticMethodName, BindingFlags.InvokeMethod, null, null, new []{ param });
		}
	}

使用方法

此处将拦截函数 Before After 和功能函数放在了同一个类中,实际使用时可以放在同一主调程序集的任意命名空间和类中。

	public class AopTestClass : AOPContext
	{
		[AOPBefore("AopTestClass", "Before")]
		[AOPAfter("AopTestClass", "After")]
		public int TestMethod1(int a, int b)
		{
			Debug.Log("Process Test 1 :" + a + "\t" + b);
			return a + b;
		}

		public static void Before(ref int a, ref int b)
		{
			Debug.Log("Start :" + a + "\t" + b);
			a = 200;
			b = 400;
		}

		public static void After(int result)
		{
			Debug.Log("End :" + result);
		}
	}

注意事项

1.通过这种方法实现AOP的类不能是静态类,但被标注AOP拦截的方法可以是静态方法。
2.反射执行处理函数,有一定性能开销,可以通过缓存函数来减少获取函数的消耗,但执行的损耗无法避免。

追加内容

AOPHandler 中的 T ReflectionUtil.GetAttribute<T>(MethodInfo info) 接口的具体实现如下:

		public static T GetAttribute<T>(MethodInfo method) where T : Attribute
		{
			var attrs = method.GetCustomAttributes(typeof (T), false);
			if (attrs.Length != 0)
			{
				var attribute = attrs[0] as T;
				if (attribute != null)
				{
					return attribute;
				}
			}
			return null;
		}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ls9512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值