目录html
1.Aop介绍java
2.Aop的基本概念git
3.Aop的织入方式web
4.Aop之静态织入编程
5.Aop之动态织入跨域
a.使用.net真实代理实现织入缓存
b.使用Unity框架的拦截器实现织入app
Aop介绍
咱们先看一下wiki百科的介绍
Traditional software development focuses on decomposing systems into units of primary functionality, while recognizing that there are other issues of concern that do not fit well into the primary decomposition. The traditional development process leaves it to the programmers to code modules corresponding to the primary functionality and to make sure that all other issues of concern are addressed in the code wherever appropriate. Programmers need to keep in mind all the things that need to be done, how to deal with each issue, the problems associated with the possible interactions, and the execution of the right behavior at the right time. These concerns span multiple primary functional units within the application, and often result in serious problems faced during application development and maintenance. The distribution of the code for realizing a concern becomes especially critical as the requirements for that concern evolve – a system maintainer must find and correctly update a variety of situations.框架
Aspect-oriented software development focuses on the identification, specification and representation of cross-cutting concerns and their modularization into separate functional units as well as their automated composition into a working system.分布式
传统的软件开发关注将系统分解成为一个主要的功能单元,然而却发现一些问题并不适合分解方式。这种传统的开发过程让编码人员合做编写主要的功能模块,以此保证那些主要的关注点可以正确的被编码。编码人员须要记住全部须要被完成的事情,如何处理每一个问题,问题可能的关联关系,以此肯定代码在正确的时候以正确的方式被执行。这些关注点在应用里面跨域了多个主要供单元,这常常在开发和维护时引起一些严重的问题。这些分布式代码致使的问题变得愈来愈迫切得须要获得解决-一个系统维护人员必须解决这种问题。
面向切面软件开发须要关注这些识别的,详细的,具备表明性的切面问题,将其模块化到功能捣衣而且自动将这些代码组合到一个工做中的系统。
英语比较蹩脚,翻译比较涩,总结起来的意思就是,Aop是将一些已经识别的切面关注的功能封装,并能自动将该功能组合到须要的地方。
我对Aop的理解就是,一些被封装好的,通用的业务单元,真正的编程人员不须要关注的部分,并能动态或静态的将这部分功能组合到业务中去。举个简单的例子,咱们在代码中,常常要判断是否用户登陆,若是未登陆,就须要跳转到指定的页面,伪代码以下:
public string GetNews(){ /*判断是否登陆,若是已经登陆,则执行后面的业务代码 若是没有登陆,则跳转到登陆页面*/ //业务代码
}
咱们能够来看一下简单的流程图
从图中咱们能够将代码中的登陆的判断业务分解成一个单独的业务单元,在须要的地方打上一个标签,告诉系统这里须要执行,那么其余编码人员就不须要再写重复相似的代码了。这就是Aop解决的问题。
在介绍Aop的实现方式前,咱们先了解一下Aop的几个知识点,这有助于咱们理解Aop的实际技术。
8)weaving(插入):是指应用aspects到一个target对象建立proxy对象的过程:complie time,classload time,runtime
目前在.NET平台中,支持的织入方式有俩中,一种是静态织入,即编译时织入,另一个是动态织入,即运行时织入。俩中方式各有优缺点,使用静态织入,能够不破坏代码结构,这里的破坏代码结构是你须要使用多余配置,写一些多余的代码,或必须依赖某种方式(这里你们也许也还不太明白,能够看完后面俩种方式的具体代码比较,再回头来看,会比较好理解)。使用动态织入的优势就是能够动态调试。俩中织入方式是互补的,即动态织入的优势也是静态织入的缺点,同理,静态织入的优势亦是动态织入的缺点。你们在作技术选型时能够根据本身的实际状况进行选择。
目前成熟的框架有PostSharp,这个框架是商业框架,意思就是须要付费,这里就不具体介绍了,须要了解的土豪请到官网查看,具体如何使用请查阅http://www.javashuo.com/tag/文档。
BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,相似PostSharp(收费),实现先后Aop切面和INotifyPropertyChanged注入方式。其原理是在编译生成IL后,借助Mono.Cecil的AssemblyDefinition读取程序集,并检测须要注入的点,并将指定的代码注入到程序集中。有想具体深刻研究的同窗,能够到 BSF.Aop中下载源码进行研究。遗憾的是这个只实现了俩个切入点,并无在异常时提供切入点。
咱们模拟一个日志记录的例子,咱们先建一个项目。
1. 在项目中引用BSF.Aop.dll,Mono.Cecil.dll,Mono.Cecil.Pdb.dll,Microsoft.Build.dll;
2. 添加一个类LogAttribute并继承Aop.Attributes.Around.AroundAopAttribute(切面);
3. 重写AroundAopAttribute的Before和After方法,并写入逻辑代码;
4. 新建一个测试类LogTest,并添加Execute方法,并在Execute方法上面添加LogAttribute标签;
5. 咱们在main里面new一个LogTest对象并调用看看输出结果;
具体的代码以下:
public class LogTest { [LogAttribute] public void Execute(int a) { a = a * 100; System.Console.WriteLine("Hello world!" + a); } } public class LogAttribute : AroundAopAttribute { public virtual void Before(AroundInfo info) { System.Console.WriteLine("Log before executed value is" + info.Params["a"]); } public virtual void After(AroundInfo info) { System.Console.WriteLine("Log after executed value is" + info.Params["a"]); } }
static void Main(string[] args) { Aop.AopStartLoader.Start(null); new LogTest().Execute(2); Console.ReadLine(); }
执行代码输出:
上例代码中
使用.NET提供的远程代理,即RealProxies来实现。
1.先建一个Aop代理类AopClassAttribute继承于ProxyAttribute,这个标签会告诉代理,这个类须要被代理建立调用;
/// <summary> /// 标记一个类为Aop类,表示该类能够被代理注入 /// </summary> public class AopClassAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { AopProxy realProxy = new AopProxy(serverType); return realProxy.GetTransparentProxy() as MarshalByRefObject; } }
2.定义Aop的属性,并定义织入点
/// <summary> /// Attribute基类,经过实现该类来实现切面的处理工做 /// </summary> public abstract class AopAttribute : Attribute { /// <summary> /// 调用以前会调用的方法 /// 1.若是不须要修改输出结果,请返回null /// 2.若是返回值不为null,则不会再调用原始方法执行,而是直接将返回的参数做为结果 /// </summary> /// <param name="args">方法的输入参数列表</param> /// <param name="resultType">方法的返回值类型</param> public abstract object PreCall(object[] args, Type resultType); /// <summary> /// 调用以后会调用的方法 /// </summary> /// <param name="resultValue">方法的返回值</param> /// <param name="args">方法的输入参数列表</param> public abstract void Called(object resultValue, object[] args); /// <summary> /// 调用出现异常时会调用的方法 /// </summary> /// <param name="e">异常值</param> /// <param name="args">方法的输入参数列表</param> public abstract void OnException(Exception e, object[] args); }
3.定义代理的逻辑过程,这里我对returnvalue作了判断,是为了实现缓存更新和添加的切面代码作的,在这里我实现了三个切入点的调用,具体可看注释部分
/// <summary> /// 主要代理处理类 /// </summary> internal class AopProxy : RealProxy { public AopProxy(Type serverType) : base(serverType) { } public override IMessage Invoke(IMessage msg) { if (msg is IConstructionCallMessage) return InvokeConstruction(msg); else return InvokeMethod(msg); } private IMessage InvokeMethod(IMessage msg) { IMethodCallMessage callMsg = msg as IMethodCallMessage; IMessage returnMessage; object[] args = callMsg.Args; var returnType = (callMsg.MethodBase as System.Reflection.MethodInfo).ReturnType;//方法返回类型 object returnValue = null;//方法返回值 AopAttribute[] attributes = callMsg.MethodBase.GetCustomAttributes(typeof(AopAttribute), false) as AopAttribute[]; try { if (attributes == null || attributes.Length == 0) return InvokeActualMethod(callMsg); //前切点 foreach (AopAttribute attribute in attributes) returnValue = attribute.PreCall(args, returnType); //若是之前切面属性都没有返回值,则调用原始的方法;不然不调用 //主要是作缓存相似的业务 if (returnValue == null) { returnMessage = InvokeActualMethod(callMsg); returnValue = (returnMessage as ReturnMessage).ReturnValue; } else returnMessage = new ReturnMessage(returnValue, args, args.Length, callMsg.LogicalCallContext, callMsg); //后切点 foreach (AopAttribute attribute in attributes) attribute.Called(returnValue,args); } catch (Exception e) {
//异常切入点 foreach (AopAttribute attribute in attributes) attribute.OnException(e, args); returnMessage = new ReturnMessage(e, callMsg); } return returnMessage; } private IMessage InvokeActualMethod(IMessage msg) { IMethodCallMessage callMsg = msg as IMethodCallMessage; object[] args = callMsg.Args; object o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args); return new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg); } private IMessage InvokeConstruction(IMessage msg) { IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage; IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg); RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue); return constructionReturnMessage; } }
4.定义上下文边界对象,想要使用Aop的类须要继承此类(这个是这种Aop方式破坏性最大的地方,由于须要继承一个类,而面向对象单继承的特性致使了业务类不能再继承其余的类。能够想象一下你有一个查询基类,而后另外一个查询类想要继承查询基类,而又想使用Aop,这时就尴尬了);
/// <summary> /// Aop基类,须要注入的类须要继承该类 /// 对代码继承有要求,后续能够改进一下 /// 注意,须要记录的不支持上下文绑定,若是须要记录,使用代理模式解决 /// </summary> public abstract class BaseAopObject : ContextBoundObject { }
5.定义Advice部分,即实际的业务逻辑,继承于AopAttribute
public class IncreaseAttribute : AopAttribute { private int Max = 10; public IncreaseAttribute(int max) { Max = max; } public override object PreCall(object[] args, Type resultType) { if (args == null || args.Count() == 0 || !(args[0] is ExampleData)) return null; var data = args[0] as ExampleData; string numString = args[0].ToString(); data.Num = data.Num * 100; Console.WriteLine(data.Num); return null; } public override void Called(object resultValue, object[] args) { if (args == null || args.Count() == 0 || !(args[0] is ExampleData)) return; var data = args[0] as ExampleData; string numString = args[0].ToString(); data.Num = data.Num * 100; Console.WriteLine(data.Num); } public override void OnException(Exception e, object[] args) { } }
public class ExampleData
{
public int Num { get; set; }
}
6.完成了上面的部分,咱们就能够来使用Aop了,定义一个须要使用Aop的类,继承于BaseAopObject,并在类上面加上[AopClass],在须要切入的方法上加上刚才定义的[IncreaseAttribute]
[AopClass] public class Example : BaseAopObject { [IncreaseAttribute(10)] public static void Do(ExampleData data) { Add(data); } [IncreaseAttribute(10)] public static ExampleData Add(ExampleData data) { return new ExampleData { Num = ++data.Num }; } }
能够看到,使用上面这种织入方式,对代码的侵入性太大,会限制代码的可扩展性。因此我比较不建议使用。
另外一种方式是借助Ioc的代理来作Aop切面注入,这里咱们以Unity做为Ioc容器,以以前写的关于Unity Ioc中的例子来介绍Aop。
1.添加AopAttribute(定义链接点),这里有个循环引用,就是AopHandler和AopAttribute之间,不过并不影响使用,若有须要你们能够本身解决一下;
/// <summary> /// 标记一个类或方法为代理,表示该类或方法能够被代理 /// </summary> [AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public abstract class AopAttribute : HandlerAttribute { /// <summary> /// 请勿重写该方法 /// </summary> /// <param name="container"></param> /// <returns></returns> public override ICallHandler CreateHandler(IUnityContainer container) { return new AopHandler(); } /// <summary> /// 调用以前会调用的方法 /// 1.若是不须要修改输出结果,请返回null,ouputs返回new object[0] /// 2.若是返回值不为null,则不会再调用原始方法执行,而是直接将返回的参数做为结果 /// </summary> /// <param name="inputArgs">方法的输入参数列表</param> /// <param name="outputs">方法中的out值,若是没有请返回null</param> /// <returns>返回值</returns> public abstract object PreCall(object[] inputArgs, out object[] outputs); /// <summary> /// 调用以后会调用的方法 /// </summary> /// <param name="resultValue">方法的返回值</param> /// <param name="inputArgs">方法的输入参数列表</param> /// <param name="outputs">方法中的out值,若是没有则该参数值为null</param> public abstract void Called(object resultValue, object[] inputArgs, object[] outputs); /// <summary> /// 调用出现异常时会调用的方法 /// </summary> /// <param name="e">异常值</param> /// <param name="inputArgs">方法的输入参数列表,键为参数名,值为参数值</param> public abstract void OnException(Exception e, Dictionary<string, object> inputArgs); }
2.添加AopHandler(代理类);
/// <summary> /// 主要代理处理类 /// </summary> internal class AopHandler : ICallHandler { public int Order { get; set; } = 1; public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { IMethodReturn returnValue = null; object attrReturnValue = null; object[] outputs = null; Dictionary<string, object> inputs = new Dictionary<string, object>(); //假若有忽略特性,直接忽略,不进行AOP代理 IgnoreAttribute[] ignoreAttributes = input.MethodBase.GetCustomAttributes(typeof(IgnoreAttribute), true) as IgnoreAttribute[]; if (ignoreAttributes != null && ignoreAttributes.Length > 0) return input.CreateMethodReturn(attrReturnValue, outputs); AopAttribute[] attributes = input.MethodBase.GetCustomAttributes(typeof(AopAttribute), true) as AopAttribute[]; try { if (attributes == null || attributes.Length == 0) return getNext()(input, getNext); for (var i = 0; i < input.Arguments.Count; i++) inputs.Add(input.Inputs.ParameterName(i), input.Inputs[i]); foreach (AopAttribute attribute in attributes) attrReturnValue = attribute.PreCall(inputs.Values.ToArray(), out outputs); //若是之前切面属性都没有返回值,则调用原始的方法;不然不调用 //主要是作缓存相似的业务 if (attrReturnValue == null) { returnValue = getNext()(input, getNext); outputs = new object[returnValue.Outputs.Count]; for (var i = 0; i < returnValue.Outputs.Count; i++) outputs[i] = returnValue.Outputs[i]; } else returnValue = input.CreateMethodReturn(attrReturnValue, outputs); if (returnValue.Exception != null) throw returnValue.Exception; foreach (AopAttribute attribute in attributes) attribute.Called(returnValue.ReturnValue, inputs.Values.ToArray(), outputs); } catch (Exception e) { foreach (AopAttribute attribute in attributes) attribute.OnException(e, inputs); returnValue = input.CreateExceptionMethodReturn(e); } return returnValue; } }
3..定义一个咱们本身的功能块(业务逻辑),这里仍是以日志为例;
public class LogAttribute : AopAttribute
{
public override void Called(object resultValue, object[] inputArgs, object[] outputs)
{
Console.WriteLine("Called");
}
public override void OnException(Exception e, Dictionary<string, object> inputArgs)
{
Console.WriteLine("exception:" + e.Message);
}
public override object PreCall(object[] inputArgs, out object[] outputs)
{
Console.WriteLine("PreCall");
outputs = new object[0];
return null;
}
}
5.接下来咱们稍微改造一下咱们的印钞机;
/// <summary> /// 印钞机 /// </summary> public class CashMachine { public CashMachine() { } public void Print(ICashTemplate template) { string templateContent = template.GetTemplate("人民币"); System.Console.WriteLine(templateContent); } } /// <summary> /// 印钞模块 /// </summary> public interface ICashTemplate { /// <summary> /// 获取钞票模板 /// </summary> /// <returns></returns> [Log] string GetTemplate(string flag); } /// <summary> /// 人民币钞票模板 /// </summary> public class CNYCashTemplate : ICashTemplate { public CNYCashTemplate() { } public string GetTemplate(string flag) { return "这是人民币模板!" + flag + " 这是返回值。"; } } /// <summary> /// 美钞钞票模板 /// </summary> public class USDCashTemplate : ICashTemplate { public USDCashTemplate() { } public string GetTemplate(string flag) { throw new Exception("哎呀,美钞模板有问题呀!"); } }
6.而后咱们在命令行的Main里改造一下;
static void Main(string[] args) { try { ICashTemplate usdTemplate = new USDCashTemplate(); ICashTemplate rmbTemplate = new CNYCashTemplate(); new CashMachine().Print(rmbTemplate); new CashMachine().Print(usdTemplate); } catch (Exception) { } Console.ReadLine(); }
7.启动一下看看结果
8.能够看到,只输出了GetTemplate方法的输出,并无输出日志,咱们要使用Ioc来注册对象才能使用,继续改造Main方法;
static void Main(string[] args) { UnityContainer container = new UnityContainer(); container.AddNewExtension<Interception>().RegisterType<ICashTemplate, CNYCashTemplate>("cny"); container.Configure<Interception>().SetInterceptorFor<ICashTemplate>("cny", new InterfaceInterceptor()); container.AddNewExtension<Interception>().RegisterType<ICashTemplate, USDCashTemplate>("usd"); container.Configure<Interception>().SetInterceptorFor<ICashTemplate>("usd", new InterfaceInterceptor()); try {new CashMachine().Print(container.Resolve<ICashTemplate>("cny")); new CashMachine().Print(container.Resolve<ICashTemplate>("usd")); } catch (Exception) { } Console.ReadLine(); }
9.启动运行,看一下结果;
能够看到,三个方法都执行了,而在抛出异常时是不会执行Called的方法的;
10.上面咱们是直接使用了UnityContainer来注册对象,而没有使用咱们以前封装的Ioc,咱们还有更简单的方式,就是采用配置的方式来注册对象和拦截器实现Aop。在实际,使用一个单独的文件来配置ioc会更易于维护。咱们先添加一个unity.config文件;
<?xml version="1.0" encoding="utf-8" ?> <unity xmlns= "http://schemas.microsoft.com/practices/2010/unity "> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> <!--注入对象--> <typeAliases> <!--表示单例--> <typeAlias alias="singleton" type="Unity.Lifetime.ContainerControlledLifetimeManager,Unity.Abstractions" /> <!--表示每次使用都进行建立--> <typeAlias alias="transient" type="Unity.Lifetime.TransientLifetimeManager,Unity.Abstractions" /> </typeAliases> <container name= "Default"> <extension type="Interception"/> <!--type表示接口 格式为 带命名空间的接口,程序集名 mapTo表示须要注入的实体类 name表示注入实体的name--> <register type= "IocWithUnity.ICashTemplate,IocWithUnity" mapTo= "IocWithUnity.CNYCashTemplate,IocWithUnity" name="cny"> <!--定义拦截器--> <interceptor type="InterfaceInterceptor"/> <policyInjection/> <!--定义对象生命周期--> <lifetime type="singleton" /> </register> <!--type表示接口 格式为 带命名空间的接口,程序集名 mapTo表示须要注入的实体类 name表示注入实体的name--> <register type= "IocWithUnity.ICashTemplate,IocWithUnity" mapTo= "IocWithUnity.USDCashTemplate,IocWithUnity" name="usd"> <!--定义拦截器--> <interceptor type="InterfaceInterceptor"/> <policyInjection/> <!--定义对象生命周期--> <lifetime type="singleton" /> </register> </container> </unity>
11.再配置app.config(WEB项目应该是web.config);
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> </configSections> <unity configSource="unity.config"/> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
12.将咱们以前写的IocContainer修改一下读取配置;
public static class IocContainer { private static IUnityContainer _container = null; static IocContainer() { _container = new UnityContainer(); object unitySection = ConfigurationManager.GetSection("unity"); if (unitySection == null) return; UnityConfigurationSection section = (UnityConfigurationSection)unitySection; section.Configure(_container, "Default"); } /// <summary> /// 注册一个实例做为T的类型 /// </summary> /// <typeparam name="T">须要注册的类型</typeparam> /// <param name="instance">须要注册的实例</param> public static void Register<T>(T instance) { _container.RegisterInstance<T>(instance); } /// <summary> /// 注册一个名为name的T类型的实例 /// </summary> /// <typeparam name="T">须要注册的类型</typeparam> /// <param name="name">关键字名称</param> /// <param name="instance">实例</param> public static void Register<T>(string name, T instance) { _container.RegisterInstance(name, instance); } /// <summary> /// 将类型TFrom注册为类型TTo /// </summary> /// <typeparam name="TFrom"></typeparam> /// <typeparam name="TTo"></typeparam> public static void Register<TFrom, TTo>() where TTo : TFrom { _container.RegisterType<TFrom, TTo>(); } /// <summary> /// 将类型TFrom注册为类型TTo /// </summary> /// <typeparam name="TFrom"></typeparam> /// <typeparam name="TTo"></typeparam> /// <typeparam name="lifetime"></typeparam> public static void Register<TFrom, TTo>(LifetimeManager lifetime) where TTo : TFrom { _container.RegisterType<TFrom, TTo>(lifetime); } /// <summary> /// 将类型TFrom注册名为name类型TTo /// </summary> /// <typeparam name="TFrom"></typeparam> /// <typeparam name="TTo"></typeparam> public static void Register<TFrom, TTo>(string name) where TTo : TFrom { _container.RegisterType<TFrom, TTo>(name); } /// <summary> /// 经过关键字name来获取一个实例对象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name"></param> /// <returns></returns> public static T Resolve<T>(string name) { return _container.Resolve<T>(name); } /// <summary> /// 获取一个为T类型的对象 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static T Resolve<T>() { return _container.Resolve<T>(); } /// <summary> /// 获取全部注册类型为T的对象实例 /// </summary> /// <typeparam name="T">须要获取的类型的对象</typeparam> /// <returns></returns> public static IEnumerable<T> ResolveAll<T>() { return _container.ResolveAll<T>(); } }
注意:配置时有一个坑 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 这句话在5.0版本后dll的名称改了,之前是Microsoft.Practices.Unity.Configuration,如今是Unity.Configuration,若是你在运行时碰到找不到文件或程序集xxx时,能够注意看一下你的具体的dll的文件名。包括后面的unity.config里面的lifetime的配置也是,你们须要注意一下本身的版本,而后找到对应的命名空间和dll文件进行配置。
13.接下来咱们运行一下看看结果如何;
总结:能够看到,静态织入方式相对较简单,对代码破坏性近乎于0,其原理大体是在编译前,将须要的代码添加到咱们添加了Attribute的地方,若是用反编译工具反编译生成的dll就能够看到实际编译后的代码。这种织入方式的缺点是不易于调试工做,由于生成的pdb文件与咱们的源代码文件其实是不同的。而采用真实代理的方式进行织入,这种方式比较原生,但对代码侵入性较大,并且效率也较低。使用ioc框架的拦截器进行拦截织入的方式,是当下比较好的一种方式,可是也是有一个约束,就是对象必须通过ioc容器来委托建立。基于这些比较,各位看官能够选择适合本身的织入方式。
本文原创,若有转载,请注明出处。