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

 

posted @ 2021-03-04 18:20  心平气又和  阅读(216)  评论(0编辑  收藏  举报