RogerTong's Tech Space

文章书傲骨,程序写春秋

导航

在前面的章节中,我们了解了如何通过DynamicProxy来实现对接口调用的拦截,在今天的实例中,我们将会看到一个具体的应用。


在我们所开发软件系统中,权限鉴别是一个非常重要的部分。通常在具体的需要权限访问的方法中,我们需要加入大段的代码来校验当前用户是否有权限操作当前的方法中的一些环节,不仅如此,我们甚至于还需要在方法的签名中加入相关的可代表用户身份的信息,例如:

  void DoOperationxx(string userId,string param1,string param2)..

在这个方法中,可能实际需要用到的参数只是"string param1,string param2",但是我们加入userId 的参数,纯粹只是为了鉴别当前用户是否有权限访问而以,导致我们的代码显得非常的冗余。

我们知道AOP的本质是对方法进行拦截,那么,我们能否在方法调用前组织一些用于鉴权的信息进行校验,校验通过后,再进入实际的调用过程呢?这个便是我们在这篇文章中要讨论的具体内容。

先来理顺一下思路及相关概念:

  1. 上下文信息(IContextInfo):我们将鉴别权限所需要的相关信息定义为上下文信息
  2. 上下文生成工厂(IContextInfoFactory):从名称上我们可以知道这个是用于生成上下文
  3. 上下文校验器(IContextValidate):用于对当前的上下文进行检查,判断是否有效

 最后,我们还需要给方法一个描述,我们希望在方法上加入一个标签,来描述这个方法用哪个校验器来进行上下文校验,例如:

[ContextValidate("UserValidate")]
void DoOperationxx(string param1,string param2)

在上面的方法中,会告知系统,在调用这个方法前,先用“UserValidate” 所标识的上下文验证器来校验当前的上下文是否有效。我们来看看与上下文相关的一些类型的定义:

 

上下文相关的定义
  1. /// <summary>   
  2. /// 打在方法上的上下文验证器标签   
  3. /// </summary>   
  4. [AttributeUsage(AttributeTargets.Method)]   
  5. public class ContextValidateAttribute:Attribute   
  6. {   
  7.     private readonly string validateKey;   
  8.   
  9.     public string ValidateKey   
  10.     {   
  11.         get { return validateKey; }   
  12.     }   
  13.   
  14.     public ContextValidateAttribute(string validateKey)   
  15.     {   
  16.         this.validateKey = validateKey;   
  17.     }   
  18. }   
  19.   
  20. //上下文信息   
  21. public interface IContextInfo   
  22. {   
  23.        
  24. }   
  25.  
  26. //上下文工厂  
  27. public interface IContextInfoFactory   
  28. {   
  29.     IContextInfo CreateContextInfo(MethodInfo methodInfo);   
  30. }   
  31.   
  32. /// <summary>   
  33. /// 上下文验证操作接口   
  34. /// </summary>   
  35. public interface IContextValidate   
  36. {   
  37.     void Validate(IContextInfo contextInfo, MethodInfo methodInfo);   
  38. }  



 根据上文相关的定义,我们需要建立这样的一种应用方式:在调用方法之前先进行拦截,用IContextInfoFactory成来生上下文,然后由识别当前方法上面的标注信息,交由相应的上下文验证器进行验证,最后,在验证无误时,再交由实际的方法来进行调用并返回,举例来说:如果我们有这样的定义:

  1. //接口定义   
  2. public interface IOperation   
  3. {   
  4.   .......   
  5. }   
  6.   
  7. //接口实现   
  8. public class Operation:IOperation   
  9. {   
  10. ......   
  11. }  


在调用时,我们会依据接口(IOperation)生成Proxy对象,在进行具体的方法调用前,我们会先设方获取真实的Operation对象,检查真实对象当前的方法标签上是否有ContextValidateAttribute标记,如果有的话,我们先设法取得上下文信息,然后,设法获取ContextValidateAttribute所标记的验证器,对上下文进行验证,如果验证出错,则抛出异常,验证通过,则调用直实对象的相应的方法并获得返回值。为了简化这一系列的过程,我们借助Castle的WindsorContainer这个IoC容器来完成装载及获得对象的工作,因此,在我们的方法拦截器中,我们假定上下文验证器及我们要调用的对象都在IoC容器中。 

接口方法拦截器
  1. public class LocalProxyInterceptor : IInterceptor   
  2. {   
  3.     private readonly IContextInfoFactory contextInfoFactory;   
  4.     private readonly IWindsorContainer container;   
  5.     private readonly string serviceKey;   
  6.   
  7.     public void Intercept(IInvocation invocation)   
  8.     {   
  9.         //检测真实的对象在容器中是否存在,如果存在,获取对象的实例及类型              
  10.         if (!container.Kernel.HasComponent(serviceKey))   
  11.             throw new NotFoundInstanceException(serviceKey);   
  12.   
  13.         object instance = container[serviceKey];   
  14.         Type instanceType = instance.GetType();   
  15.   
  16.         //组合调用的参数类型,用于查找到实例中对应的方法   
  17.         List<Type> paramTypes = new List<Type>();   
  18.         foreach (ParameterInfo paramInfo in invocation.Method.GetParameters())   
  19.             paramTypes.Add(paramInfo.ParameterType);   
  20.   
  21.         const BindingFlags FindFlags =   
  22.             BindingFlags.FlattenHierarchy |   
  23.             BindingFlags.Instance |   
  24.             BindingFlags.NonPublic |   
  25.             BindingFlags.Public;   
  26.   
  27.         MethodInfo methodInfo =   
  28.             instanceType.GetMethod(invocation.Method.Name,   
  29.                                    FindFlags, null, paramTypes.ToArray(), null);   
  30.   
  31.         if (methodInfo == null)   
  32.             throw new NotFoundMethodException(invocation.Method.Name);   
  33.   
  34.         //如果在实例的方法指明了上下文验证器,则生成对应的上下文信息并进行验证   
  35.         if (Attribute.IsDefined(methodInfo, typeof (ContextValidateAttribute)))   
  36.         {   
  37.             ContextValidateAttribute validateAttr =   
  38.                 (ContextValidateAttribute)   
  39.                 Attribute.GetCustomAttribute(methodInfo,   
  40.                                              typeof (ContextValidateAttribute));   
  41.   
  42.             if (!container.Kernel.HasComponent(validateAttr.ValidateKey))   
  43.                 throw new NotFoundInstanceException(validateAttr.ValidateKey);   
  44.   
  45.             IContextValidate contextValidate =   
  46.                 (IContextValidate) container[validateAttr.ValidateKey];   
  47.   
  48.   
  49.             contextValidate.Validate(   
  50.                 contextInfoFactory.CreateContextInfo(methodInfo), methodInfo);   
  51.         }   
  52.   
  53.         //调用容器中实例相对应的方法,并设置返回值   
  54.         invocation.ReturnValue =   
  55.             methodInfo.Invoke(instance,invocation.Arguments);   
  56.     }   
  57.   
  58.     public LocalProxyInterceptor(string serviceKey,    
  59.         IWindsorContainer container, IContextInfoFactory contextInfoFactory)   
  60.     {   
  61.         this.contextInfoFactory = contextInfoFactory;   
  62.         this.serviceKey = serviceKey;   
  63.         this.container = container;   
  64.     }   
  65. }  


有了拦截器,我们再来定义Proxy工厂接口及实现
 

  1. //Proxy对象工厂的接口定义   
  2. public interface ILocalProxyFactory   
  3. {   
  4.     //根据指定的容器Key、上下文生成工厂来建立Proxy对象   
  5.     T CreateProxy<T>(string serviceKey,IContextInfoFactory contextInfoFactory);   
  6. }   
  7.   
  8. //Proxy对象工厂的接口实现   
  9. public class LocalProxyFactory:ILocalProxyFactory   
  10. {   
  11.     private readonly IWindsorContainer container;   
  12.   
  13.     public LocalProxyFactory(IWindsorContainer container)   
  14.     {   
  15.         this.container = container;   
  16.     }   
  17.   
  18.   
  19.     public T CreateProxy<T>(string serviceKey, IContextInfoFactory contextInfoFactory)   
  20.     {   
  21.         IInterceptor _interceptor =   
  22.             new LocalProxyInterceptor(serviceKey, container, contextInfoFactory);   
  23.         ProxyGenerator proxyGenerator = new ProxyGenerator();   
  24.         return proxyGenerator.CreateInterfaceProxyWithoutTarget<T>(_interceptor);   
  25.     }   
  26. }  

 

接下来,我们来做一下完整的实现,先来实现上下文的接口
 

C#代码
  1. public class ContextInfo:IContextInfo   
  2. {   
  3.     private readonly string userName;   
  4.   
  5.     public string UserName   
  6.     {   
  7.         get { return userName; }   
  8.     }   
  9.   
  10.     public ContextInfo(string userName)   
  11.     {   
  12.         this.userName = userName;   
  13.     }   
  14. }   
  15.   
  16. public class ContextInfoFactory:IContextInfoFactory   
  17. {   
  18.     private string userName;   
  19.   
  20.     public string UserName   
  21.     {   
  22.         get { return userName; }   
  23.         set { userName = value; }   
  24.     }   
  25.   
  26.     public ContextInfoFactory(string userName)   
  27.     {   
  28.         this.userName = userName;   
  29.     }   
  30.   
  31.     public IContextInfo CreateContextInfo(MethodInfo methodInfo)   
  32.     {   
  33.         return new ContextInfo(userName);   
  34.     }   
  35. }   
  36.   
  37. //实现一个简单的上下文验证器,判断用户是否为 "Roger",如果不是则抛异常   
  38. public class UserValidate:IContextValidate   
  39. {   
  40.     public void Validate(IContextInfo contextInfo, MethodInfo methodInfo)   
  41.     {   
  42.         if (contextInfo == null || !(contextInfo is ContextInfo))   
  43.             throw new InvalidContextInfoException();   
  44.   
  45.         ContextInfo info = (ContextInfo) contextInfo;   
  46.         if (info.UserName != "Roger")   
  47.             throw new Exception("UserName Invalid");   
  48.     }   
  49. }  


再来完成一个简单的调用服务
 

  1. public interface IShowMessage   
  2. {   
  3.     void Calc(int x, int y);   
  4. }   
  5.   
  6. public class ShowMessage:IShowMessage   
  7. {   
  8.     //指示调用当前的方法前必须通过"UserValidate"进行上下文验证   
  9.     [ContextValidate("UserValidate")]   
  10.     public void Calc(int x, int y)   
  11.     {   
  12.         Console.WriteLine("Client Call {0} + {1}!", x, y);   
  13.     }   
  14. }  


最后,我们来看看Main函数部分的程序
 

  1. static void Main()   
  2. {   
  3.     WindsorContainer container = new WindsorContainer();   
  4.        
  5.     //将ShowMessage服务装入IoC容器中   
  6.     container.AddComponent("ShowMessage",    
  7.         typeof (IShowMessage), typeof (ShowMessage));   
  8.        
  9.     //将UserValidate这个上下文验证器装入IoC容器中   
  10.     container.AddComponent("UserValidate",    
  11.         typeof (IContextValidate), typeof (UserValidate));   
  12.   
  13.     try  
  14.     {   
  15.         //建立上下文工厂   
  16.         ContextInfoFactory contextInfoFactory = new ContextInfoFactory("Roger");   
  17.   
  18.         //建立Propxy对象工厂   
  19.         ILocalProxyFactory localProxyFactory = new LocalProxyFactory(container);   
  20.   
  21.         //获到IShowMessage的Proxy对象   
  22.         IShowMessage showMessage =   
  23.             localProxyFactory.CreateProxy<IShowMessage>("ShowMessage", contextInfoFactory);   
  24.         showMessage.Calc(300, 400);   
  25.   
  26.         //改变当前用户的名称,让验证器抛异常   
  27.         contextInfoFactory.UserName = "Windy";   
  28.         showMessage.Calc(300, 400);   
  29.     }   
  30.     catch(Exception ex)   
  31.     {   
  32.         Console.WriteLine("ExceptionType:{0},ExceptionMessage:{1}",   
  33.                           ex.GetType(), ex.Message);   
  34.     }   
  35.   
  36.     Console.ReadLine();   
  37. }   

运行结果如下:


本文始发于:http://www.rogertong.cn/article.asp?id=28

点击下载源程序

 

Ps: 周一要去上海一趟,上海的兄弟们记得请喝酒啊。