AOP
AOP
AOP简介
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP实现原理
- 静态实现=》装饰器(层层装饰)/代理模式(一个代理),在于处理场景不同,侧重点不同。
- 动态实现=》Remoting/Castle(Emit)=》把对象拿过来(动态生成)=》思路和静态类似
- 静态织入=》PostSharp(收费)=》一扩展编译工具,生成的时候加入额外的代码(类似语法糖)
- 依赖注入容器的AOP扩展=》接口=配置文件
- MVC的Filter=》特性标记,然后该方法执行前/后就多了逻辑,通过invoker调用中心负责反射调用方法=》检查特性,有则执行额外逻辑(实现也是最简单的)
静态实现
装饰器模式实现静态代理
//订单类
public class Order
{
public int Id { set; get; }
public string Name { set; get; }
public int Count { set; get; }
public double Price { set; get; }
public string Desc { set; get; }
}
//订单接口 抽象
public interface IOrderProcessor
{
//业务逻辑
void Submit(Order order);
}
//订单业务逻辑 实现
public class OrderProcessor : IOrderProcessor
{
public void Submit(Order order)
{
Console.WriteLine("提交订单");
}
}
//装饰器模式提供一个AOP功能 装饰器模式是23中设计模式种,结构型模式的巅峰之作,组合+继承
public class OrderProcessorDecorator : IOrderProcessor //继承=》接口
{
//订单业务逻辑属性 组合=》到内部
public IOrderProcessor OrderProcessor { get; set; }
//构造函数
public OrderProcessorDecorator(IOrderProcessor orderprocessor)
{
OrderProcessor = orderprocessor;
}
//1.继承业务逻辑接口的时候会实现业务逻辑
//2.使用组合进来的业务对象实现对应的业务逻辑
//3.在这个业务逻辑前后添加上自己想要的功能
//业务逻辑
public void Submit(Order order)
{
PreProceed(order);
OrderProcessor.Submit(order);
PostProceed(order);
}
//业务逻辑之前
public void BeforeProceed(Order order)
{
Console.WriteLine("提交订单前,进行订单数据校验....");
if (order.Price < 0)
{
Console.WriteLine("订单总价有误,请重新核对订单。");
}
}
//业务逻辑之后
public void AfterProceed(Order order)
{
Console.WriteLine("提交带单后,进行订单日志记录......");
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "提交订单,订单名称:" + order.Name + ",订单价格:" + order.Price);
}
}
static void Main(string[] args)
{
//订单类
Order order = new Order() { Id = 1, Name = "lee", Count = 10, Price = 100.00, Desc = "订单测试" };
//装饰器模式实现的静态代理
IOrderProcessor orderprocessor = new OrderProcessorDecorator(new OrderProcessor());
//调用,装饰器模式实现的静态代理,调用对应的业务逻辑
orderprocessor.Submit(order);
Console.ReadLine();
}
代理模式实现静态代理
//订单类
public class Order
{
public int Id { set; get; }
public string Name { set; get; }
public int Count { set; get; }
public double Price { set; get; }
public string Desc { set; get; }
}
//订单接口
public interface IOrderProcessor
{
void Submit(Order order);
}
//订单业务逻辑
public class OrderProcessor : IOrderProcessor
{
public void Submit(Order order)
{
Console.WriteLine("提交订单");
}
}
//订单静态代理实现
public class OrderProcessorProxy : IOrderProcessor
{
//业务逻辑字段
private IOrderProcessor _OrderProcessor = new OrderProcessor();
//1.继承业务逻辑接口的时候会实现业务逻辑
//2.使用业务逻辑字段
//3.在这个业务逻辑前后添加上自己想要的功能
//业务逻辑
public void Submit(Order order)
{
PreProceed(order);
_OrderProcessor.Submit(order);
PostProceed(order);
}
public void PreProceed(Order order)
{
Console.WriteLine("提交订单前,进行订单数据校验....");
if (order.Price < 0)
{
Console.WriteLine("订单总价有误,请重新核对订单。");
}
}
public void PostProceed(Order order)
{
Console.WriteLine("提交带单后,进行订单日志记录......");
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "提交订单,订单名称:" + order.Name + ",订单价格:" + order.Price);
}
}
static void Main(string[] args)
{
//初始化订单类
Order order = new Order() { Id = 1, Name = "lee", Count = 10, Price = 100.00, Desc = "订单测试" };
//初始化代理模式实现的 AOP思想
IOrderProcessor orderProcessor = new OrderProcessorProxy();=======
orderprocessor.Submit(order);
Console.ReadLine();
}
动态实现
//01接口 订单处理器接口
public interface IOrderProcessor
{
void Submit(Order order);
}
//02具体的实现类 订单处理器
//MarshalByRefObject 表示上下文的绑定对象,允许远程调用,可以访问边界=》一旦继承了这个类,那么整个类可以被我们序列化。
//把这块内存序列化成一个东西传到远程的机器,远程的机器再把东西序列化成一个别的东西,就可以调用这个对象,把调用的结果再传回来。
//核心:本地序列化=》远程=》远程再在收到的内容前后做AOP处理
public class OrderProcessor:MarshalByRefObject,IOrderProcessor
{
//03 实现接口
public void Submit(Order order)
{
Console.WriteLine("提交订单");
}
}
/// <summary>
/// 真实代理 固定写法
/// </summary>
/// <typeparam name="T"></typeparam>
public class MyRealProxy<T> : RealProxy
{
//04 将透明代理传进来的对象包装
private T tTarget;
public MyRealProxy(T target)
: base(typeof(T))
{
this.tTarget = target;
}
public override IMessage Invoke(IMessage msg)
{
//执行前
BeforeProceede(msg);
//调用原有方法
IMethodCallMessage callMessage = (IMethodCallMessage)msg;
object returnValue = callMessage.MethodBase.Invoke(this.tTarget,callMessage.Args);
//执行后
AfterProceede(msg);
return new ReturnMessage(returnValue,new object[0],0,null,callMessage);
}
public void BeforeProceede(IMessage message)
{
Console.WriteLine("方法执行前可以加入的逻辑");
}
public void AfterProceede(IMessage message)
{
Console.WriteLine("方法执行后可以加入的逻辑");
Console.ReadLine();
}
}
/// <summary>
/// 透明代理 固定写法
/// </summary>
public static class TransparentProxy
{
//03 创建对象
//0-1 给个类型
public static T Create<T>()
{
//0-2 创建一个对象
T instance = Activator.CreateInstance<T>();
//0-3 通过真实代理进行包装
MyRealProxy<T> realProxy = new MyRealProxy<T>(instance);
//0-4 得到一个新对象
T transparenProxy = (T)realProxy.GetTransparentProxy();
//0-5 返回一个新对象 新对象和它类型一致,是他的子类
return transparenProxy;
}
}
static void Main(string[] args)
{
Order order = new Order { Id = 1, Name = "苹果", Pirce = 20, Count = 2, Desc = "订单测试" };
OrderProcessor orderProcessor = TransparentProxy.Create<OrderProcessor>();
orderProcessor.Submit(order);
}
Unity容器实现
配置文件实现 代码实现 ,配置文件实现是代码中经常用到的
- 顺序问题,配置文件的的注册顺序是调用顺序然后才是业务方法,但扩展逻辑可以在业务方法后;
- 接口方法不需要某个AOP扩展,需要跳过
- 判断方法
- 用特性,接口声明的时候使用特性,例如[Obsolete]特性,然后在AOP内找到该特性 input.Target.GetType().GetCustomAttributes()。
- 配置文件可以简写,
NuGet包引用
Prism.Unity
Unity.Configuration
Unity.Interception
Unity.Interception.Configuration
01 业务逻辑抽象 IUserBll
public interface Itest
{
void show();
}
02 业务逻辑实现
public class test : Itest
{
public void show()
{
Console.WriteLine("======================业务逻辑====================");
Console.WriteLine("这里是业务逻辑");
Console.WriteLine("======================业务逻辑====================");
}
}
03 引入Unity容器
static void Main(string[] args)
{
Print();
}
static void Print()
{
#region 引入Unity容器,这是一段固定写法 作用是读取当前应用程序运行目录下的Unity.Config配置文件,配置一个容器
//1. 初始化UnityContainer容器
IUnityContainer container = new UnityContainer();
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
//2. 开始读取配置文件
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
#endregion
//01 使用UnityConfigurationSection 配置(容器,名称)方法代替 配置容器 将容器和配置文件中的别名关联"
configSection.Configure(container, "aopContainer");
//02 通过容器创建对象
Itest processor = container.Resolve<Itest>();
//02 调用业务方法
processor.show();
}
04 设置配置文件
<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>
<container name="aopContainer">
<extension type="Interception"/>
<register type="AOP.Itest,AOP" mapTo="AOP.test,AOP">
<interceptor type="InterfaceInterceptor"/>
<interceptionBehavior type="AOP.MonitorBehavior,AOP"/>
<interceptionBehavior type="AOP.LogBehavior,AOP"/>
</register>
</container>
</containers>
</unity>
</configuration>
常用模板
CfgFiles.Unity.config配置
<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>
<container name="aopContainer">
<extension type="Interception"/>
<register type="业务逻辑抽象命名空间,业务逻辑所在程序集" mapTo="业务逻辑,业务逻辑所在程序集">
<interceptor type="InterfaceInterceptor"/>
<interceptionBehavior type="AOP类名,AOP类所在程序集"/>
</register>
</container>
</containers>
</unity>
</configuration>
C#代码创建容器
static void Print()
{
#region 引入Unity容器,这是一段固定写法 作用是读取当前应用程序运行目录下的Unity.Config配置文件,配置一个容器
//1. 初始化UnityContainer容器
IUnityContainer container = new UnityContainer();
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
//2. 开始读取配置文件
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
#endregion
//01 使用UnityConfigurationSection 配置(容器,名称)方法代替 配置容器 将容器和配置文件中的别名关联"
configSection.Configure(container, "aopContainer");
//02 通过容器创建对象
Itest processor = container.Resolve<Itest>();
//02 调用业务方法
processor.show();
}
c#AOP类
public class LogBehavior : IInterceptionBehavior
{
public bool WillExecute
{
get { return true; }
}
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
#region 业务逻辑前执行
Console.WriteLine("\r\n");
Console.WriteLine("=================耗时统计=========================");
Console.WriteLine("正在记录日志,请稍后······");
#endregion
#region 分割线
var method = getNext().Invoke(input, getNext);
#endregion
#region 业务逻辑后执行
Console.WriteLine("=================耗时统计=========================");
#endregion
return method;
}
}
问题处理
异常:
The type name or alias Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration could not be resolved.Please check your configuration file and verify this type name.
解决方法:
- Nuget引用Unity.Interception.Configuration这个包
- 路径中不能有中文
- 接口和实现接口的类的设置成相同的命名空间
Demo地址
https://github.com/volador816/AOP
随笔
- POP面向过程编程:符合逻辑思维,线性的处理问题,无法应对复杂的系统。
- OOP面向对象编程:万物皆对象,对象交互完成功能,功能叠加成模块,模块组成系统,区搭建附加的大型软件系统。
- AOP面向切面编程:允许开发者动态的修改静态的OO模型,动态的修改类,就像现实生活中对象在生命周期中会不断的改变自身的,AOP是一种编程思想。
正是因为能动态的扩展功能,所以在程序设计时就可以有一下的好处:
- 聚焦核心业务逻辑,权限/异常/日志/缓存/事务,通用功能可以通过AOP方式田佳,程序设计简单,几种管理,代码复用。
- 功能动态扩展,几种管理,代码复用,规范化。
思考:为什么需要AOP,AOP可以解决什么问题?
一个典型对象(静态,牢不可破)
属性:ID Name
行为:Study Eat
以来抽象:ISports
静态的固定整体,可以修改属性值,可以复写行为,可以替换抽象实现,但是类自身是稳定不变的
砖=》墙=》房间=》大厦
类=》功能点=》模块=》系统
如何理解静态?砖应该是稳定的,静态的,否则房子就不稳定。
遇到的问题:类却是会变化的,例如:增加日志/异常/权限/缓存/事务
是否只能修改类?
所以才有了GOF的23中设计模式,应对变化,设计模式的核心套路就是依赖抽象,细节就可以变化。
但是只能替换整个对象,但是没办法把一个类动态改变。