面向切面编程AOP
一、引言:
面向切面编程,自我理解是:在软件系统实现过程中,在很多模块操作中都会用到一些相同的固定的逻辑操作,比如权限验证、日志记录、性能检测等,这些都是公共的逻辑,贯穿整个系统实现过程中。面向切面编程就是将这些公共的逻辑和系统本身核心的业务逻辑分离开来集中管理,这样一方面对减轻系统本身的业务逻辑,另一方面降低耦合度,提高可重用性,便于后期扩展维护。
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(百度百科)
二、静态拦截方式实现AOP
静态拦截方式最为简单,只是从原理上面描述AOP的实现过程。
public class Order { public int Id { set; get; } public string Name { set; get; } public int Count { set; get; } public double Price { set; get; } public string Desc { set; get; } } public interface IOrderProcessor { void Submit(Order order); } public class OrderProcessor : IOrderProcessor { public void Submit(Order order) { Console.WriteLine("提交订单"); } } public class OrderProcessorDecorator : IOrderProcessor { public IOrderProcessor OrderProcessor { get; set; } public OrderProcessorDecorator(IOrderProcessor orderprocessor) { OrderProcessor = orderprocessor; } public void Submit(Order order) { PreProceed(order); OrderProcessor.Submit(order); PostProceed(order); } public void PreProceed(Order order) { Console.WriteLine("提交订单前,进行订单数据校验...."); if (order.Price < 0) { Console.WriteLine("订单总价有误,请重新核对订单。"); } } public void PostProceed(Order order) { Console.WriteLine("提交带单后,进行订单日志记录......"); Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "提交订单,订单名称:" + order.Name + ",订单价格:" + order.Price); } }
调用方式:
Order order = new Order() { Id = 1, Name = "lee", Count = 10, Price = 100.00, Desc = "订单测试" }; IOrderProcessor orderprocessor = new OrderProcessorDecorator(new OrderProcessor()); orderprocessor.Submit(order);
三、动态代理实现AOP
using Microsoft.Practices.Unity.InterceptionExtension; using Microsoft.Practices.Unity; using Microsoft.Practices.EnterpriseLibrary.PolicyInjection; namespace ConsoleDmeo.AOP { public class User { public string Name { set; get; } public string PassWord { set; get; } } #region 1、定义特性方便使用 public class LogHandlerAttribute : HandlerAttribute { public string LogInfo { set; get; } public int Order { get; set; } public override ICallHandler CreateHandler(IUnityContainer container) { return new LogHandler() { Order = this.Order, LogInfo = this.LogInfo }; } } #endregion #region 2、注册对需要的Handler拦截请求 public class LogHandler : ICallHandler { public int Order { get; set; } public string LogInfo { set; get; } //这个方法就是拦截的方法,可以规定在执行方法之前和之后的拦截 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Console.WriteLine("LogInfo内容" + LogInfo); //0.解析参数 var arrInputs = input.Inputs; if (arrInputs.Count > 0) { var oUserTest1 = arrInputs[0] as User; } //1.执行方法之前的拦截 Console.WriteLine("方法执行前拦截到了"); //2.执行方法 var messagereturn = getNext()(input, getNext); //3.执行方法之后的拦截 Console.WriteLine("方法执行后拦截到了"); return messagereturn; } } #endregion #region 3、用户定义接口和实现 public interface IUserOperation { void Test(User oUser); void Test2(User oUser, User oUser2); } //这里必须要继承这个类MarshalByRefObject,否则报错 public class UserOperation : MarshalByRefObject, IUserOperation { private static UserOperation oUserOpertion = null; public UserOperation() { //oUserOpertion = PolicyInjection.Create<UserOperation>(); } //定义单例模式将PolicyInjection.Create<UserOperation>()产生的这个对象传出去,这样就避免了在调用处写这些东西 public static UserOperation GetInstance() { if (oUserOpertion == null) oUserOpertion = PolicyInjection.Create<UserOperation>(); return oUserOpertion; } //调用属性也会拦截 public string Name { set; get; } //[LogHandler],在方法上面加这个特性,只对此方法拦截 [LogHandler(LogInfo = "Test的日志为aaaaa")] public void Test(User oUser) { Console.WriteLine("Test方法执行了"); } [LogHandler(LogInfo = "Test2的日志为bbbbb")] public void Test2(User oUser, User oUser2) { Console.WriteLine("Test2方法执行了"); } } #endregion }
调用方式:
var oUserTest1 = new ConsoleDmeo.AOP.User() { Name = "test2222", PassWord = "yxj" }; var oUserTest2 = new ConsoleDmeo.AOP.User() { Name = "test3333", PassWord = "yxj" }; var oUser = UserOperation.GetInstance(); oUser.Test(oUserTest1); oUser.Test2(oUserTest1, oUserTest2);
四、MVC中的filter
在MVC项目中filter实现的AOP,简单介绍:
自定义授权类:
public class MyAuthorizeAttribute:AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { filterContext.Result = new RedirectResult("/Login/Index"); base.OnAuthorization(filterContext); } protected override bool AuthorizeCore(HttpContextBase httpContext) { string userName = httpContext.User.Identity.Name; if (userName == "admin") { return true; } else { return false; } } }
上面自定义了请求授权方法和自定义授权检查,在局部控制器中使用:
[MyAuthorize] public ActionResult Index() { return View(); }
五、WCF中的消息拦截
在WCF的系统中,在客户端和服务器端互相通信的过程中,客户端和服务端互相调用,其中程序员需要对调用过程进行控制,例如客户端在发送请求前可以先进行日志记录等,整个过程本人感觉和面向切面编程的定义非常相符,因此拿到此处一并进行记录。要对SOAP消息进行拦截和修改,需要实现两个接口,它们都位于System.ServiceModel.Dispatcher (程序集System.ServiceModel)。下面分别介绍:
接口一:IClientMessageInspector
从名字中我们可以猜测,它是用来拦截客户消息的,而看看它的方法,你就更加肯定当初的猜测了。
- BeforeSendRequest:向服务器发送请求前拦截或修改消息(事前控制)
- AfterReceiveReply:接收到服务器的回复消息后,在调用返回之前拦截或修改消息(事后诸葛亮)
接口二:IDispatchMessageInspector
刚才那个接口是针对客户端的,而这个是针对服务器端的。
- AfterReceiveRequest:接收客户端请求后,在进入操作处理代码之前拦截或修改消息(欺上)
- BeforeSendReply:服务器向客户端发送回复消息之前拦截和修改消息(瞒下)。
下面是在网上找到的一个实例,已经在本文底部进行引用。
首先创建一个简单的WCF的服务:
[ServiceContract] public interface IServiceTest { [OperationContract] int AddInt(int a, int b); [OperationContract] Student GetStudent(); [OperationContract] CalResultResponse ComputingNumbers(CalcultRequest inMsg); }
服务的实现:
public class ServiceTest : IServiceTest { public int AddInt(int a, int b) { return a + b; } public Student GetStudent() { Student stu = new Student(); stu.StudentName = "小明"; stu.StudentAge = 22; return stu; } public CalResultResponse ComputingNumbers(CalcultRequest inMsg) { CalResultResponse rmsg = new CalResultResponse(); switch (inMsg.Operation) { case "加": rmsg.ComputedResult = inMsg.NumberA + inMsg.NumberB; break; case "减": rmsg.ComputedResult = inMsg.NumberA - inMsg.NumberB; break; case "乘": rmsg.ComputedResult = inMsg.NumberA * inMsg.NumberB; break; case "除": rmsg.ComputedResult = inMsg.NumberA / inMsg.NumberB; break; default: throw new ArgumentException("运算操作只允许加、减、乘、除。"); break; } return rmsg; } }
编写wcf的消息拦截器:
引用:
using System.ServiceModel; using System.ServiceModel.Dispatcher; using System.ServiceModel.Description; using System.ServiceModel.Channels;
拦截器及行为:
/// <summary> /// WCF消息拦截 /// </summary> public class MyMessageInspector:IClientMessageInspector,IDispatchMessageInspector { void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState) { Console.WriteLine("客户端接收到的回复:\n{0}", reply.ToString()); } object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel) { Console.WriteLine("客户端发送请求前的SOAP消息:\n{0}", request.ToString()); return null; } object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { Console.WriteLine("服务器端:接收到的请求:\n{0}", request.ToString()); return null; } void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState) { Console.WriteLine("服务器即将作出以下回复:\n{0}", reply.ToString()); } } /// <summary> /// 插入到终结点的Behavior /// </summary> public class MyEndPointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { // 不需要 return; } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 植入“偷听器”客户端 clientRuntime.ClientMessageInspectors.Add(new MyMessageInspector()); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { // 植入“偷听器” 服务器端 endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector()); } public void Validate(ServiceEndpoint endpoint) { // 不需要 return; } }
服务端:新建一个控制台解决方案,绑定wcf:
Uri baseAddress = new Uri("http://192.168.5.111:11378/services"); // 声明服务器主机 ServiceHost host = new ServiceHost(typeof (ServiceTest)); WSHttpBinding binding = new WSHttpBinding(); host.AddServiceEndpoint(typeof (IServiceTest), binding, baseAddress); if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) { ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); behavior.HttpGetEnabled = true; behavior.HttpGetUrl = baseAddress; host.Description.Behaviors.Add(behavior); } // 把自定义的IEndPointBehavior插入到终结点中 foreach (var endpont in host.Description.Endpoints) { endpont.EndpointBehaviors.Add(new Demolib.MyEndPointBehavior()); } host.Open(); Console.WriteLine("服务已启动。");
客户端:
ServiceTest.ServiceTestClient client = new ServiceTestClient(); client.Endpoint.EndpointBehaviors.Add(new Demolib.MyEndPointBehavior()); // 记得在客户端也要插入IEndPointBehavior
// 1、调用带元数据参数和返回值的操作 Console.WriteLine("\n20和35相加的结果是:{0}", client.AddInt(20, 35)); // 2、调用带有数据协定的操作 ServiceTest.Student student = client.GetStudent(); Console.WriteLine("\n学生信息---------------------------"); Console.WriteLine("姓名:{0}\n年龄:{1}", student.StudentName, student.StudentAge); // 3、调用带消息协定的操作 Console.WriteLine("\n15乘以70的结果是:{0}", client.ComputingNumbers("乘", 15, 70));
完毕
引用: