AOP概念,拦截,与UnityInterception基础应用
本文基于这篇
https://www.cnblogs.com/xuf22/articles/3029345.html
言简意赅地介绍了装饰模式、拦截概念、与通过InterceptionBehavior使用UnityInterception的基本方式
----------------------------------------------------------------------------------
下面是一些理解,以及原文代码注释
AOP(Aspect Oriented Programming),面向切面编程
看一些场景
服务端各式各样的方法,挨个写日志——每个方法都是不同的,但写日志的行为是相同的;
各种登录类——各种登录方式是不同的,但鉴权的行为是相同的;
封装一个线程安全的通用操作类——各种数据结构、操作都是不同的,但锁的申请和释放,是相同的;
不难发现这些功能是横竖交织的
类自己实际的功能是竖着的,而每种操作都要附加的日志、鉴权、锁等行为是横着的;
而在竖着的类的内部,横着的行为往往是有序的,并且代码相同——这些行为,即可以看成是“切面”
以传统的面向对象(OOP)或面向过程(POP)的思想,会非常冗余且繁琐——而且这些方法未必在同一个类里
当然可以写一个基类,里边带一个Operate抽象函数,让所有业务类都派生于它,并各自重写
在单一继承的Java和C#中,甚至只能写重复代码(比如挨个类写打日志函数)
而AOP的思想,是着眼于过程,把这些横着的相同“切面”抽象出来,让竖着的类只实现它自己的核心功能
并通过某种方式——在【运行中】、或【编译时】,在不改动核心类代码的前提下,为核心类附着功能切面。
Unity中的拦截功能,就在【运行中】实现了AOP思想
通过Unity配置或编码,Unity可以拦截类方法,在执行该方法的前后做一些事——使得方法可以正常专注于编写自己的功能
并且在嵌套拦截的时候,会以 外层前拦截 -> 内层前拦截 -> 外层方法 -> 内层方法 -> 外层方法 -> 内层后拦截 -> 外层后拦截 的链式逻辑处理——总之是按最合理的方式
通过Unity框架进行拦截
Unity提供两种方式实现拦截功能:
一种是实现IInterceptionBehavior(开头引用文章内示例):这种方式比较隐蔽,重写拦截行为可对任何符合条件的目标生效——当需要对某dll的所有方法,都进行统一的拦截处理时,应考虑这种方式
另一种是通过[ICallHandler]特性——可以较为灵活的为不同方法制定不同拦截策略,甚至是多种策略。
以下分别演示这两种拦截的写法。
【示例1】通过InterceptionBehavior(对引用文章代码的注释)实现拦截
/* * 附着TransparentProxyInterceptor,并通过IInterceptionBehavior处理拦截 * * * 代码来自 * https://www.cnblogs.com/xuf22/articles/3029345.html * 原文非常言简意赅地介绍了 装饰模式, 和 使用Unity拦截并用IInterceptionBehavior处理 的基本方式 * 这里只取拦截器部分,关于装饰模式,我将它拆分到了《经典示例/装饰模式》里 * * * 相比于必须通过HandleAttribute标识的ICallHandler方式,这种方式更隐式一些,需要: * 1、实现IInterceptionBehavior类,作为对目标的拦截行为 * 2、为Unity容器配置拦截扩展,以及为目标对象配置拦截器,和自定义的拦截行为; * * * 但对该目标的拦截行为也就被限定为这一种,目前不知能否为同一个对象挂多个Behavior。 * * * 本例中配置的拦截器为TransparentProxyInterceptor,可以拦截目标对象的所有操作(方法、属性、字段) * 当它的拦截目标是个接口时与InterfaceInterceptor效果似乎没区别,但如果它的目标是个类,则要求必须派生于MarshalByRefObject。 */ namespace ConsoleApp1 { //被拦截的类,必须继承MarshalByRefObject public class MyClassForProxyInterception : MarshalByRefObject { public string Width { get; set; } public void Method() { Console.WriteLine("invoke:MyClassForProxyInterception.Method"); } public int Height; } //被拦截后的行为 public class MyInterceptionBehavior : IInterceptionBehavior { //如果为false,则本次拦截完全跳过,直接执行核心调用 public bool WillExecute => true; public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } //实际的拦截操作 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { Console.WriteLine("The method of target object has been intercepted!"); return getNext()(input, getNext); } } class Program { static void Main(string[] args) { IUnityContainer container = new UnityContainer(); UnityConfigurationSection unitySection = (UnityConfigurationSection)ConfigurationManager.GetSection("unity"); unitySection.Configure(container, "MyInterception"); var obj = container.Resolve<MyClassForProxyInterception>();//构造函数不触发拦截 obj.Method();//触发拦截 obj.Width = "100px";//触发拦截 obj.Height = 7;//触发拦截 Console.Read(); } } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> </configSections> <unity> <!--通过别名的方式简写命名空间与程序集--> <alias alias="MyClass" type="ConsoleApp1.MyClassForProxyInterception, ConsoleApp1"/> <alias alias="MyInterceptionBehavior" type="ConsoleApp1.MyInterceptionBehavior, ConsoleApp1"/> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/> <container name="MyInterception"> <extension type="Interception"/> <!--这个类型注册到了容器,但并不会Resolve,也就不需要写mapTo--> <register type="MyClass"> <!--这个类型的拦截器可以接受类,写在这里标识它要对“MyClass(上边alias进行指代)”类进行拦截--> <interceptor type="TransparentProxyInterceptor"/> <interceptionBehavior name="MyBehavior" type="MyInterceptionBehavior"/> <policyInjection/> </register> </container> </unity> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> </configuration>
【示例1】通过【ICallHandler】特性实现拦截
假定有一dll,提供IUser类功能实现;
我们需要:在不改动dll代码的前提下,调用IUser中的每个接口时,打印一条日志。
/*被拦截的DLL,不修改代码*/ // 被拦截的接口 public interface IUser { void GetAll(); } public class User : IUser { public User(IDAL dal) => _dal = dal; IDAL _dal; public void GetAll() { _dal.GA(); Console.WriteLine("..Service.."); } } //接口内部逻辑 public interface IDAL { void GA(); } public class DAL : IDAL { public void GA() { Console.WriteLine(".dal."); } }
/*实现拦截,并调用*/ public class LogHandler : ICallHandler { public int Order { get; set; } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Console.WriteLine($"{input.Target} -> {input.MethodBase.Name}"); return getNext()(input, getNext); } } class Program { static void Main(string[] args) { IUnityContainer container = new UnityContainer(); //流式加载,不用配置文件 container.AddNewExtension<Interception>() .RegisterType<IUser, User>() .RegisterType<IDAL, DAL>() .Configure<Interception>() .SetInterceptorFor<IUser>(new InterfaceInterceptor()) .AddPolicy("myPolicy")//name相当于id .AddMatchingRule<AssemblyMatchingRule>(new InjectionConstructor(new InjectionParameter(typeof(string), "ClassLibrary1"))) .AddCallHandler<LogHandler>()//参数不加new ContainerControlledLifetimeManager(),否则所有拦截都调用这同一个对象 ; //配置文件加载 //(ConfigurationManager.GetSection("unity") as UnityConfigurationSection).Configure(container); IUser user = container.Resolve<IUser>(); user.GetAll(); } }
如果上方使用配置文件(可选)
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> </configSections> <!--这个xmlns似乎写不写都行--> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> <container> <extension type="Interception"/> <interception> <!--name必须得有但是没用--> <policy name="myPolicy"> <!--name必须得有但是没用,重点是后边type,提供程序集级别的过滤--> <matchingRule name="assemblyMatchingRule" type="AssemblyMatchingRule"> <constructor> <!--仅对assemblyName = "ClassLibrary1"--> <param name="assemblyName" value="ClassLibrary1"/> </constructor> </matchingRule> <!--为匹配到的内容挂上LogHandler,如果name相同,则只有一个会生效--> <callHandler name="logHandler" type="ClassLibrary1.LogHandler, ClassLibrary1"/> <!--这会挂上第二个,日志会打两次,但不知道这里怎么设置Order,直接加特性的话会报错--> <callHandler name="logHandler2" type="ClassLibrary1.LogHandler, ClassLibrary1"/> </policy> </interception> <register type="ClassLibrary1.IUser, ClassLibrary1" mapTo="ClassLibrary1.User, ClassLibrary1"> <!--表示用这个InterfaceInterceptor拦截IProductDao,这种拦截器要求被拦截的目标必须是个接口--> <interceptor type="InterfaceInterceptor"/> <policyInjection/> </register> <!--这个类型没用拦截器,所以不会被拦截--> <register type="ClassLibrary1.IDAL, ClassLibrary1" mapTo="ClassLibrary1.DAL, ClassLibrary1"/> </container> </unity> </configuration>
执行结果(第一句就是进入函数前的日志打印)
下一篇详细测试ICallHandler方式的各种特性
https://www.cnblogs.com/xinpingqiyouhe/p/14489705.html