AOP之Unity学习纪要

AOP概念:

  • Aspect-Oriented Programming,面向切面的编程;
  • 比较专业的说法:它是可以通过预编译方式和运行期间动态代理实现,在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。实现跨越应用程序多个模块的功能需求;
  • 为了便于理解,通俗的说,在原业务流程中,新增一个功能(我理解为新增一个旁路,即便没有也不影响业务流转),这条旁路也可以附加在其他多个业务流程上,只需修改Unity配置文件以及业务实体的创建就能实现,比如新增或修改日志、验证用户权限、增加缓存、异常拦截功能等这些通用且可能变动的功能,不用挨个修改业务类,且这些通用的功能都是一个单独的模块;
  • Unity默认提供了三种拦截器来实现aop:TransparentProxyInterceptor、InterfaceInterceptor、VirtualMethodInterceptor,以InterfaceInterceptor为例,实现了这个接口的类里的所有方法,均新增了旁路;

Unity运用步骤

  • 本文不仅用Unity创建对象,还是通过Unity实现AOP给方法增加一些通用功能,如仅用于创建对象,去掉XML配置文件里的interceptionBehavior节点(即不新增额外功能),也可以参考

博客园的 IOC容器:Unity

  • NuGet引用UnityUnity.ConfigurationUnity.InterceptionUnity.Interception.Configuration
  • 新增一个XML配置文件(InterfaceInterceptor方式),通过配置文件定义某种接口由哪种类来实例化,同时定义功能(旁路)由哪个类提供,记得把属性改为始终复制;
  • 新增一个接口,再新增一个类实现接口,即实现接口里的业务逻辑;
  • 新增功能(旁路)类
    • 类名称通常用Behavior结尾,参考下面的范例,编写处理逻辑,比如日志功能、异常拦截功能等;
    • getNext().Invoke(input, getNext)指向配置文件下一个interceptionBehavior节点,如果是最后一个interceptionBehavior则指向实际业务对象的方法;
    • 另外可以根据方法特性来判断是否执行,如果特性标记在实例类里,则判断方法:bool re = input.Target.GetType().GetMethod(input.MethodBase.Name).IsDefined(typeof(NoAOPAttribute), true);
    • 如果特性标记在接口里,则用:input.MethodBase.IsDefined()来判断;
    • 如果需要抛出异常,就使用:return input.CreateExceptionMethodReturn(new Exception("错误描述"));比直接用new Exception("错误描述")更能准确反馈异常位置,记得要加 return;
    • 异常捕获interceptionBehavior节点,在XML配置文件里通常放第1个;
  • 主程序里
    • 初始化(new)一个IUnityContainer容器对象:
    • 容器对象通过Unity方式读取配置并配置对象;
    • 通过容器实例化(不可以直接new,而是某接口 对象名称 = container.Resolve<某接口>())具体的业务对象,此业务对象再调用方法;
    • 调用方法后,会先进入旁路,再回原函数执行(这个顺序可调整的);

部分代码

  • XML配置文件样本,文件名起名:Unity.config,新增3个功能,对应3个类
<configuration>
	<configSections>
		<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
	</configSections>
	<unity>
		<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
		<containers>
			<!--testContainer,这个名称,在主程序里会用到,用于读取配置-->
			<container name="testContainer">
				<extension type="Interception"/>
				<!--定义某种接口由哪种类来实例化-->
				<!--name用于一个接口,多个不同类实现的区分-->
				<register type="Zoulei.AOP.DEMO.IStudy,Zoulei.AOP.DEMO" mapTo="Zoulei.AOP.DEMO.Student, Zoulei.AOP.DEMO" name="str">
					<interceptor type="InterfaceInterceptor"/>
					<!--逗号前面是接口类型的完全限定名:命名空间+接口名称,逗号后面是DLL文件的名称 name解决同一个接口不同实例问题-->
					<!-- 异常处理功能(旁路) 1-->
					<interceptionBehavior type="Zoulei.AOP.DEMO.ExceptionBehavior,Zoulei.AOP.DEMO"/>
					<!-- 权限验证功能(旁路) 2-->
					<interceptionBehavior type="Zoulei.AOP.DEMO.PermissionBehavior,Zoulei.AOP.DEMO"/>
					<!-- 日志功能(旁路) 3-->
					<interceptionBehavior type="Zoulei.AOP.DEMO.LogBehavior,Zoulei.AOP.DEMO"/>
				</register>
			</container>
		</containers>
	</unity>
</configuration>
  • Unity读取配置步骤太多,特记下来:
IUnityContainer container = new UnityContainer();
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "Unityconfig/Unity.config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection unityConfigurationSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
unityConfigurationSection.Configure(container, "testContainer"); //testContainer在XML配置文件里有

– 新增功能(旁路)类,日志类范例

    public class LogBehavior: IInterceptionBehavior
    {
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }
		//处理逻辑在此方法内,其他方法时框架自有的,不用修改
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
        	//是否执行逻辑的判断,即如果类方法有NoAOPAttribute特性,则不执行逻辑
            bool re = input.Target.GetType().GetMethod(input.MethodBase.Name).IsDefined(typeof(NoAOPAttribute), true);
            if (!re)
            {
                //新增日志处理逻辑 do something...
            }
            //下一步,依据XML配置上下顺序,流转到下一个interceptionBehavior节点,如果已经是最后一个interceptionBehavior节点,则getNext().Invoke(input, getNext)指向业务实体的方法
            return getNext().Invoke(input, getNext);
        }
        
        public bool WillExecute
        {
            get { return true; }
        }
    }
  • 异常类范例
    public class ExceptionBehavior : IInterceptionBehavior
    {
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }
        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            //先执行其他所有逻辑,如果有异常,则程序再异常处return,中断执行,返回到这里
            IMethodReturn methodReturn = getNext().Invoke(input, getNext);

            //最后判断是否有无异常
            Console.WriteLine("ExceptionBehavior 异常");
            if (methodReturn.Exception == null)
            {
                Console.WriteLine("无异常");
            }
            else
            {
                Console.WriteLine($"异常信息:{methodReturn.Exception.Message }");
            }
            return methodReturn;
        }
        
        public bool WillExecute
        {
            get { return true; }
        }
    }
  • 主程序调用
//前面是读取XML配置文件,上面有现成范例
//实现接口IStudy的对象创建,而不是直接`new`,通过容器创建对象
//IStudy study = container.Resolve<IStudy>("str");这里的str要与XML配置文件里的register节点定义的一致;
IStudy study = container.Resolve<IStudy>("");
Student student = new Student { Id = 100, Name = "张三" };
//调用方法,会先执行XML配置文件里的interceptionBehavior定义的功能,再执行此方法
study.Study(student);
原文地址:https://www.cnblogs.com/zoulei0718/p/14315580.html