AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
配置可以通过xml文件来进行,大概有四种方式:
1. 配置ProxyFactoryBean,显式地设置advisors, advice, target等
2. 配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3. 通过<aop:config>来配置
4. 通过<aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点
AOP的简单应用
为了说明AOP的工作原理,博主打算先从一个简单的例子开始,通过静态拦截的方式来了解AOP是如何工作的。
1、静态拦截
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); } }
调用代码:
static void Main(string[] args) { Order order = new Order() { Id = 1, Name = "lee", Count = 10, Price = 100.00, Desc = "订单测试" }; IOrderProcessor orderprocessor = new OrderProcessorDecorator(new OrderProcessor()); orderprocessor.Submit(order); Console.ReadLine(); }
2、动态代理
了解了静态拦截的例子,你是否对AOP有一个初步的认识了呢。下面我们就来到底AOP该如何使用。按照园子里面很多牛人的说法,AOP的实现方式大致可以分为两类:动态代理和IL 编织两种方式。博主也不打算照本宣科,分别拿Demo来说话吧。下面就以两种方式各选一个代表框架来说明。
动态代理方式,博主就以微软企业库(MS Enterprise Library)里面的PIAB(Policy Injection Application Block)框架来作说明。
首先需要下载以下几个dll,然后添加它们的引用。
然后定义对应的Handler
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
最后我们来看调用的代码:
static void Main(string[] args) { try { var oUserTest1 = new User() { Name = "test2222", PassWord = "yxj" }; var oUserTest2 = new User() { Name = "test3333", PassWord = "yxj" }; var oUser = UserOperation.GetInstance(); oUser.Test(oUserTest1); oUser.Test2(oUserTest1,oUserTest2); } catch (Exception ex) { //throw; } }
得到结果如下:
我们来看执行Test()方法和Test2()方法时候的顺序。
由于Test()和Test2()方法上面加了LogHander特性,这个特性里面定义了AOP的Handler,在执行Test和Test2方法之前和之后都会进入Invoke()方法里面。其实这就是AOP的意义所在,将切面的通用功能在统一的地方处理,在主要逻辑里面直接用过特性使用即可。
3、IL编织
静态织入的方式博主打算使用PostSharp来说明,一来这个使用起来简单,二来项目中用过这种方式。
Postsharp从2.0版本就开始收费了。为了说明AOP的功能,博主下载了一个免费版本的安装包,使用PostSharp与其它框架不太一样的是一定要下载安装包安装,只引用类库是不行的,因为上文说过,AOP框架需要为编译器或运行时添加扩展。使用步骤如下:
(1)下载Postsharp安装包,安装。
(2)在需要使用AOP的项目中添加PostSharp.dll 这个dll的引用。
(3)定义拦截的方法:
[Serializable] public class TestAop : PostSharp.Aspects.OnMethodBoundaryAspect { //发生异常时进入此方法 public override void OnException(MethodExecutionArgs args) { base.OnException(args); } //执行方法前执行此方法 public override void OnEntry(MethodExecutionArgs args) { base.OnEntry(args); } //执行方法后执行此方法 public override void OnExit(MethodExecutionArgs args) { base.OnExit(args); } }
注意这里的TestAop这个类必须要是可序列化的,所以要加上[Serializable]特性
(4)在需要拦截功能的地方使用。
在类上面加特性拦截,此类下面的所有的方法都会具有拦截功能。
[TestAop]public class Impc_TM_PLANT : Ifc_TM_PLANT { /// <summary> /// 获取或设置服务接口。 /// </summary> private Ic_TM_PLANTService service { get; set; } public IList<DTO_TM_PLANT> Find() { DTO_TM_PLANT otest = null; otest.NAME_C = "test";//异常,会进入OnException方法
return service.FindAll(); } }
方法上面加特性拦截,只会拦截此方法。
[TestAop] public IList<DTO_TM_PLANT> Find() { DTO_TM_PLANT otest = null; otest.NAME_C = "test"; return service.FindAll(); }
有没有感觉很简单,很强大,其实这一简单应用,解决我们常见的日志、异常、权限验证等功能简直太小菜一碟了。当然Postsharp可能还有许多更加高级的功能,有兴趣可以深究下。
4、MVC里面的Filter
public class AOPFilterAttribute : ActionFilterAttribute, IExceptionFilter { public void OnException(ExceptionContext filterContext) { throw new System.NotImplementedException(); } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); } }
在controller里面使用该特性:
[AOPFilter] public JsonResult GetEditModel(string strType) { var lstRes = new List<List<DragElementProp>>(); var lstResPage = new List<PageProperty>(); //.........todo return Json(new { lstDataAttr = lstRes, PageAttr = lstResPage, lstJsConnections = lstJsPlumbLines }, JsonRequestBehavior.AllowGet); }
调试可知,在执行GetEditModel(string strType)方法之前,会先执行OnActionExecuting()方法,GetEditModel(string strType)之后,又会执行OnActionExecuted()方法。这在我们MVC里面权限验证、错误页导向、日志记录等常用功能都可以方便解决。