20181122_C#中AOP_使用Unity实现AOP
一. 使用Unity的AOP实现
a) 整体项目截图:
b) 添加Unity的Nuget包, 直接使用最新版就行, 需要添加两个 Unity 和 Unity.Interception (这个是为AOP做的一个扩展)
c) AOP配置文件, 详细注释(CfgFiles\Unity.Config)
1 <!--这是一个标准Unity配置文件的格式--> 2 3 <configuration> <!--根节点名称--> 4 <configSections> 5 <!--name容器的名称 type 表示如何查找节点, 这是固定写法--> 6 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> 7 </configSections> 8 <unity> 9 <!--下面一行是固定写法, 用这个写法来支持AOP--> 10 <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> 11 <containers><!--定义一组容器--> 12 <container name="aopContainer"> 13 <!--定义一个容器; 表示在IUserProcessor这个接口下所有的方法都给他增加, 这个容器(aopContainer)内所包含的方法--> 14 <extension type="Interception"/> 15 <!--这里使用Interception类型的扩展; 其实在Unity中有三种类型来处理这件事; 这种是属于实现接口的方法, 还有一种是继承父类; 第三种是使用Virtual虚方法的形式, 使用虚方法有点类似于CastleProxyAOP这个代理--> 16 <!--把接口映射到具体的实现类; 也就是说告诉容器如果遇到了MyAOP.UnityWay.IUserProcessor这个抽象, 就使用MyAOP.UnityWay.UserProcessor来帮我们实例化--> 17 <!--MyAOP.UnityWay.IUserProcessor完整的接口名称, MyAOP是程序所在的dll名称, 程序集的名称(当然在这里是当前exe文件的名称--> 18 <!--MyAOP.UnityWay.UserProcessor具体的实现类, MyAOP和上面的解释一样--> 19 <!--MyAOP是程序集的名称--> 20 <!-- register type="MyAOP.UnityWay.IUserProcessor,MyAOP" mapTo="MyAOP.UnityWay.UserProcessor,MyAOP" 这一句整体的意思就是告诉容器, 如果遇到IUserProcessor这个抽象, 就使用UserProcessor类来帮我进行初始化一个具体的实例出来--> 21 <register type="MyAOP.UnityWay.IUserProcessor,MyAOP" mapTo="MyAOP.UnityWay.UserProcessor,MyAOP"> 22 <!--Unity中的AOP有三种模式, 第一种必须继承MarshalByRefObject这个父类的AOP 23 第二种必须是虚方法的AOP 24 第三种就是下面写的接口形式的 InterfaceInterceptor 一般都用这种接口形式的: 25 表示只要是接口下的所有方法, 都会被增加上下面的方法 26 --> 27 <interceptor type="InterfaceInterceptor"/> <!--AOP支持的模式; 推荐使用这种接口模式的--> 28 <!--下面这五个表示 在IUserProcessor接口中的所有方法, 都拥有下面方法的扩展--> 29 30 <!--异常处理的; 注意如果你想全局处理异常, 则应该把异常处理的Behavior放到最顶层, 否则在Unity中自己抛出的异常则会抓不到--> 31 <interceptionBehavior type="MyAOP.UnityWay.ExceptionLoggingBehavior, MyAOP"/> 32 <!--缓存的behavior; 注意缓存的Behavior也应该放在记录日志之前; 需要注意的是, 如果被缓存命中了, 那么后面的AOP也不会执行的--> 33 <interceptionBehavior type="MyAOP.UnityWay.CachingBehavior, MyAOP"/> 34 <!--方法执行前写日志--> 35 <interceptionBehavior type="MyAOP.UnityWay.LogBeforeBehavior, MyAOP"/> 36 <!--参数检查--> 37 <interceptionBehavior type="MyAOP.UnityWay.ParameterCheckBehavior, MyAOP"/> 38 <!--方法执行后做的事情--> 39 <interceptionBehavior type="MyAOP.UnityWay.LogAfterBehavior, MyAOP"/> 40 </register> 41 </container> 42 </containers> 43 </unity> 44 </configuration>
d) Model代码:
1 public class User 2 { 3 public int Id { get; set; } 4 public string Name { get; set; } 5 public string Password { get; set; } 6 }
e) IuserProcessor代码:
1 public interface IUserProcessor 2 { 3 void RegUser(User user); 4 User GetUser(User user); 5 }
f) UserProcessor代码:
1 public class UserProcessor : IUserProcessor 2 { 3 public void RegUser(User user) 4 { 5 Console.WriteLine("用户已注册。"); 6 //throw new Exception("11"); 7 } 8 [Obsolete] 9 public User GetUser(User user) 10 { 11 return user; 12 } 13 }
g) LogBeforeBehavior代码(在方法执行之前写日志):
1 /// <summary> 2 /// 1. 标准的通过AOP写日志的功能, 必须实现IInterceptionBehavior; 这个接口来自于Unity容器 3 /// </summary> 4 public class LogBeforeBehavior : IInterceptionBehavior 5 { 6 /// <summary> 7 /// 3. 此方法为固定写法 8 /// </summary> 9 /// <returns></returns> 10 public IEnumerable<Type> GetRequiredInterfaces() 11 { 12 return Type.EmptyTypes; 13 } 14 15 /// <summary> 16 /// 4. 关键方法 17 /// </summary> 18 /// <param name="input"></param> 19 /// <param name="getNext"></param> 20 /// <returns></returns> 21 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 22 { 23 Console.WriteLine("LogBeforeBehavior"); 24 //5. 如果有的方法想用, 有的方法不想被AOP, 则可以在这里做切断 25 26 //这里可以将某个方法打上特性,然后获取MemberInfo中的特性信息, 以此来判断哪个 27 //方法需要AOP切入,哪些方法不需要AOP切入 28 Console.WriteLine(input.MethodBase.Name); 29 // Console.WriteLine( input.MethodBase.GetCustomAttributes(true)[0].ToString() ); 30 foreach (var item in input.Inputs) //input中包含了所有的参数信息 31 { 32 Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); 33 //反射&序列化获取更多信息 34 } 35 // Unity.Interception.InterceptionBehaviors.InvokeInterceptionBehaviorDelegate gn = getNext(); 36 // return getNext()(input, getNext);//注意这种写法也是可以的; 这种写法的意思就是getNext()方法返回了一个委托, 然后委托又被调用了 37 return getNext().Invoke(input, getNext);//4. 固定写法 38 39 // return null; 40 //return gn.Invoke(input, getNext); 41 //getNext()表示执行完当前的方法之后, 去执行原始方法; 但是注意的是, getNext()中可能有多个方法 42 } 43 44 45 /// <summary> 46 /// 2. 固定写法 47 /// </summary> 48 public bool WillExecute 49 { 50 get { return true; } 51 } 52 }
h) LogAfterBehavior代码(方法执行之后写日志):
1 //IInterceptionBehavior来自于AOP容器中 Unity.Interception.InterceptionBehaviors; 2 public class LogAfterBehavior : IInterceptionBehavior 3 { 4 /// <summary> 5 /// 固定写法 6 /// </summary> 7 /// <returns></returns> 8 public IEnumerable<Type> GetRequiredInterfaces() 9 { 10 return Type.EmptyTypes; 11 } 12 13 /// <summary> 14 /// 可以记录调用时间, 参数, 函数名称 15 /// </summary> 16 /// <param name="input"></param> 17 /// <param name="getNext"></param> 18 /// <returns></returns> 19 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 20 { 21 //getNext()(input, getNext); 关键点; 如果业务代码写在这句话之前, 那么就会先执行业务代码, 再执行配置文件中配置的实例代码; 反之同理 22 23 IMethodReturn methodReturn = getNext().Invoke(input, getNext);//执行后面的全部动作 24 //原始方法执行后 25 26 Console.WriteLine("LogAfterBehavior"); 27 Console.WriteLine(input.MethodBase.Name); 28 foreach (var item in input.Inputs) 29 { 30 Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(item)); 31 //反射&序列化获取更多信息 32 } 33 Console.WriteLine("LogAfterBehavior" + methodReturn.ReturnValue); 34 return methodReturn; 35 } 36 37 /// <summary> 38 /// 固定写法 39 /// </summary> 40 public bool WillExecute 41 { 42 get { return true; } 43 } 44 }
i) ParameterCheckBehavior代码(在方法执行之前进行参数校验):
1 /// <summary> 2 /// 参数检查 3 /// </summary> 4 public class ParameterCheckBehavior : IInterceptionBehavior 5 { 6 public IEnumerable<Type> GetRequiredInterfaces() 7 { 8 return Type.EmptyTypes; 9 } 10 11 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 12 { 13 Console.WriteLine("ParameterCheckBehavior"); 14 User user = input.Inputs[0] as User;//可以不写死类型,反射+特性完成数据有效性监测 15 if (user.Password.Length < 10)//可以过滤一下敏感词 16 { 17 //返回一个异常 18 return input.CreateExceptionMethodReturn(new Exception("密码长度不能小于10位")); 19 //注意只要抛出异常, 那么后面的都不会再执行了, 在这里也就是说后的 logafterbehavior是不会再执行了 20 //throw new Exception("密码长度不能小于10位"); 21 } 22 else 23 { 24 Console.WriteLine("参数检测无误"); 25 return getNext().Invoke(input, getNext); 26 } 27 } 28 29 public bool WillExecute 30 { 31 get { return true; } 32 } 33 }
j) ExceptionLoggingBehavior代码(异常处理):
1 /// <summary> 2 /// 异常处理 3 /// </summary> 4 public class ExceptionLoggingBehavior : IInterceptionBehavior 5 { 6 public IEnumerable<Type> GetRequiredInterfaces() 7 { 8 return Type.EmptyTypes; 9 } 10 11 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 12 { 13 IMethodReturn methodReturn = getNext()(input, getNext); 14 15 Console.WriteLine("ExceptionLoggingBehavior"); 16 if (methodReturn.Exception == null) //检查methodReturn中是否有异常 17 { 18 Console.WriteLine("无异常"); 19 } 20 else 21 { 22 Console.WriteLine($"异常:{methodReturn.Exception.Message}"); 23 } 24 return methodReturn; 25 } 26 27 public bool WillExecute 28 { 29 get { return true; } 30 } 31 }
k) CachingBehavior代码(缓存处理):
1 /// <summary> 2 /// 缓存AOP 3 /// </summary> 4 public class CachingBehavior : IInterceptionBehavior 5 { 6 public IEnumerable<Type> GetRequiredInterfaces() 7 { 8 return Type.EmptyTypes; 9 } 10 /// <summary> 11 /// 定义缓存字典 12 /// </summary> 13 private static Dictionary<string, object> CachingBehaviorDictionary = new Dictionary<string, object>(); 14 15 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) 16 { 17 Console.WriteLine("CachingBehavior"); 18 //把方法的名称和所有的参数列表全部标识为key , 然后放到字典里面; 19 string key = $"{input.MethodBase.Name}_{Newtonsoft.Json.JsonConvert.SerializeObject(input.Inputs)}"; 20 //当方法名和参数都不变, 则表示有缓存 21 22 23 if (CachingBehaviorDictionary.ContainsKey(key)) 24 { 25 return input.CreateMethodReturn(CachingBehaviorDictionary[key]);//直接返回 [短路器] 不再往下; 包括后面的Behavior也不会再执行了; 因为全部被直接短路了 26 } 27 else 28 { 29 //如果字典中没有, 则继续执行这里 30 IMethodReturn result = getNext().Invoke(input, getNext); 31 if (result.ReturnValue != null) //并将其加入到缓存列表中去; 当然缓存中放一个null没有任何意义, 所以这里判断一下 32 { 33 //不存在则, 加入缓存中 34 CachingBehaviorDictionary.Add(key, result.ReturnValue); 35 } 36 37 return result; 38 } 39 40 41 } 42 43 public bool WillExecute 44 { 45 get { return true; } 46 } 47 }
l) UnityConfigAOP代码(调用):
1 /// <summary> 2 /// 使用EntLib\PIAB Unity 实现动态代理 3 /// 4 /// 1. 添加引用→右键→添加 NuGet包, 添加Unity的包引用 5 /// 2. 这里添加的 Unity是5.8.13的版本, 而Unity.Interception是5.5.0; 因为最新的(5.5.5)不支持 .net 4.0 6 /// 3. Unity是一个 Unity的容器; 而Unity.Interception当Unity做AOP时的一个扩展 7 /// </summary> 8 public class UnityConfigAOP 9 { 10 public static void Show() 11 { 12 User user = new User() 13 { 14 Name = "孙悟空", 15 Password = "12345678934534643" 16 }; 17 18 19 { 20 //1. 初始化UnityContainer容器 21 IUnityContainer container = new UnityContainer(); 22 ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); 23 24 //2. 开始读取配置文件 25 fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config"); 26 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); 27 UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); 28 29 30 //3. 使用配置文件中aopContainer节点下的所有配置信息来初始化container这个AOP容器 31 configSection.Configure(container, "aopContainer"); //10. 这个aopContainer就是配置文件中的 <container name="aopContainer"> 这里这个容器的名称 ; 注意如果配置文件中的名字和这里的名字不一样就会报出以下错误: 32 // The container named "aopContainer" is not defined in this configuration section. 33 34 // 在这里创建对象, 创建的规则就来自于配置文件的 <register type="MyAOP.UnityWay.IUserProcessor,MyAOP" mapTo="MyAOP.UnityWay.UserProcessor,MyAOP"> 这一行; 如果是 IUserProcessor 类型, 就是使用UserProcessor 来创建实例 35 IUserProcessor processor = container.Resolve<IUserProcessor>(); 36 processor.RegUser(user); //4. 注意, 当程序运行到这里的时候, 正常情况应该是去调用userProcessor的Reguser方法, 但是由于在配置文件中进行了配置, 所以它会去执行 LogBeforeBehavior 的 Invoke的方法(当aopContainer这容器中只有一个LogBeforeBehavior的时候, 如果多个, 则那个方法在前面则优先执行, 其它的上依次的调) 37 User userNew1 = processor.GetUser(user); //调用GetUser的时候, 也会执行配置文件中对应配置了的方法 38 39 40 //演示缓存Behavior 41 User userNew2 = processor.GetUser(user); 42 } 43 } 44 }