AOP概念:
- Aspect-Oriented Programming,面向切面的编程;
- 比较专业的说法:它是可以通过预编译方式和运行期间动态代理实现,在不修改源代码的情况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。AOP是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。实现跨越应用程序多个模块的功能需求;
- 为了便于理解,通俗的说,在原业务流程中,新增一个功能(我理解为新增一个旁路,即便没有也不影响业务流转),这条旁路也可以附加在其他多个业务流程上,只需修改Unity配置文件以及业务实体的创建就能实现,比如新增或修改日志、验证用户权限、增加缓存、异常拦截功能等这些通用且可能变动的功能,不用挨个修改业务类,且这些通用的功能都是一个单独的模块;
- Unity默认提供了三种拦截器来实现aop:TransparentProxyInterceptor、InterfaceInterceptor、VirtualMethodInterceptor,以InterfaceInterceptor为例,实现了这个接口的类里的所有方法,均新增了旁路;
Unity运用步骤
- 本文不仅用Unity创建对象,还是通过Unity实现AOP给方法增加一些通用功能,如仅用于创建对象,去掉XML配置文件里的
interceptionBehavior
节点(即不新增额外功能),也可以参考
- NuGet引用
Unity
、Unity.Configuration
、Unity.Interception
、Unity.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<某接口>()
)具体的业务对象,此业务对象再调用方法; - 调用方法后,会先进入旁路,再回原函数执行(这个顺序可调整的);
- 初始化(new)一个
部分代码
- 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);