IronPython 中的属性注入器机制

了解了一点 IronPython for ASP.NET CTP 的朋友都知道,在 IronPython for ASP.NET(以下 IronPython 简称 IP)中我们可以这样写代码:

#  操作子控件
formView1.txtBox1.Text  =   " Hello "

#  获取 Request 变量
id  =  Request.userId

#  对于 DataRow:
name  =  row.Name

#  

而如果用 C#,则必须这样写:
//  操作子控件
(formView1.FindControl( " txtBox1 " as  TextBox).Text  =   " Hello " ;

//  获取 Request 变量
string  sId  =  Request[ " userId " ];
int  id  =   0 ;
try {
  id 
=   int .Parse(sId);
}
catch {
  
//  
}

//  对 DataRow 的操作
string  name  =  ( string ) row[ " Name " ];

//  

这两段代码一对比,差别是巨大的。 白皮书上说这个原理叫做 属性注入器(Attributes Injector)。注入器本质上是对 Python 中的 Qualification 语法(对象.属性)做了自定义或者说拦截。其内部会调用 __getattr__ 和 __setattr__ 方法。

本文就尝试来分析一下属性注入器的原理,其中的大部分代码可以用 Reflector 配合 FileDissembler 反编译 Microsoft.Web.IronPython.dll 得到,另外结合 IronPython 引擎的源代码也做了一些分析。

首先建立一个 IronPython for ASP.NET 项目,我们来看看 web.config,

<!--  添加 httpModule  -->
< httpModules >
  
< add  name ="DynamicLanguageHttpModule"  type ="Microsoft.Web.IronPython.DynamicLanguageHttpModule" />
</ httpModules >

这里指定了 HttpModule 为 DynamicLanguageHttpModule 类,其关键代码如下:

internal   class  DynamicLanguageHttpModule: IHttpModule, IBuildProvider {
    
static  DynamicLanguageHttpModule() {
        
//  

        
//  以下利用 Ops 类的方法给一些 CLR 类型注册属性注入器
        Ops.RegisterAttributesInjectorForType( typeof (Control),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (ScriptPage),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (ScriptUserControl),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (ScriptMaster),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (FormView),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (CreateUserWizard),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (DataListItem),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (GridViewRow),  new  ControlAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (HttpRequest),  new  RequestParamsAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (NameValueCollection),  new  NameValueCollectionAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (StateBag),  new  DictionaryAttributesInjector(),  false );
        Ops.RegisterAttributesInjectorForType(
typeof (DataRowView),  new  DataRowViewAttributesInjector(),  false );

        Type type1 
=  HttpContext.Current.Request.QueryString.GetType();
        Ops.RegisterAttributesInjectorForType(type1, 
new  NameValueCollectionAttributesInjector(),  false );

        
//  
    }
}

上面可以看到,在这个 HttpModule 的静态构造器里面,进行了一系列属性注入器的注册工作,这个注册动作是由 Ops.RegisterAttributesInjectorForType() 方法来完成的。至于该方法的原理,我们下面再解释,现在先看一下这几个属性注入器是如何实现的。

首先需要明确的是,这里的属性注入器并不对应到每一个具体类型,而是对应到能够从中获取属性的基类。这样,属性注入器就被归纳成了很有限的几种。比如,Control, ScriptPage, ScriptUserControl, ScriptMaster 本质上都是控件,我们要实现的是对控件的子控件进行提取(简化 FindControl 动作),因此它们就共用了一个 ControlAttributesInjector.

这里,IP for ASP.NET 一共实现了 5 种属性注入器,其功能分别如下:

1. ControlAttributesInjector
    让 Control 及上面注册的一些子类可以简单获取其子孙控件。

2. DataRowViewAttributesInjector
    注入 DataRowView 的属性获取动作。

3.  DictionaryAttributesInjector
    注入可转化为 IDictionary 的对象的属性获取动作

4. NameValueCollectionAttributeInjector
    注入 NameValueCollection 及注册子类的属性获取动作

5. RequestParamsAttributesInjector
    注入对 HttpRequest 对象的属性获取动作

以上五种注入器,都实现了 IAttributesInjector 接口。而这个接口是在 IP 引擎中定义的,其内容如下:

namespace  IronPython.Runtime {
    
public   interface  IAttributesInjector {
        
//  得到所有属性名称的列表
        List GetAttrNames( object  self);

        
//  以符号 (SymboId) 为键,获得属性值
         bool  TryGetAttr( object  self, SymbolId name,  out   object  value);
    }
}

可以看到,这个接口中定义了对属性的获取动作,而这里也正是注入的入口。
我们以 ControlAttributesInjector 为例看一下其实现方法:

public   class  ControlAttributesInjector: IAttributesInjector {

    
//  递归的添加控件集合中所有子控件的 ID 到 list 中。
     private   static   void  GetControlIDsRecursive(ControlCollection controls, List list) {
        
foreach  (Control control1  in  controls) {
            
if  ( ! string .IsNullOrEmpty(control1.ID)) {
                list.Add(control1.ID);
            }
            
if  (control1.HasControls()  &&   ! (control1  is  INamingContainer)) {
                ControlAttributesInjector.GetControlIDsRecursive(control1.Controls, list);
            }
        }
    }

    
//  显式实现 IAttributesInjector 接口:获取所有属性名称的列表。(内部通过添加 obj 这个控件的子孙控件 ID 来实现)
    List IAttributesInjector.GetAttrNames( object  obj) {
        Control control1 
=  obj  as  Control;
        List list1 
=   new  List();
        ControlAttributesInjector.GetControlIDsRecursive(control1.Controls, list1);
        
return  list1;
    }

    
//  显式实现 IAttributesInjector 接口:尝试获取某个名称的控件。目标(obj)可以是 Control 或 Page.
    
//  内部用 FindControl 来实现
     bool  IAttributesInjector.TryGetAttr( object  obj, SymbolId nameSymbol,  out   object  value) {
        Control control1 
=  obj  as  Control;
        Page page1 
=  control1  as  Page;
        
if  (page1  !=   null ) {
            ScriptMaster master1 
=  page1.Master  as  ScriptMaster;
            
if  (master1  !=   null ) {
                
foreach  ( string  text1  in  master1.ContentPlaceHolders) {
                    value 
=  ((ContentPlaceHolder)master1.FindControl(text1)).FindControl(nameSymbol.GetString());
                    
if  (value  !=   null ) {
                        
return   true ;
                    }
                }
                value 
=   null ;
                
return   false ;
            }
        }
        value 
=  control1.FindControl(nameSymbol.GetString());
        
if  (value  ==   null ) {
            
return   false ;
        }
        
return   true ;
    }
}

代码的作用正如前面叙述的一样,比较简单。其他的注入器和这个原理类似,也都很简单,不详细叙述。

下面我们回过头来看一下,在 HttpModule 里面调用 Ops.RegisterAttributesInjectorForType() 方法后,发生了什么。这些注入器是如何发挥效用的。

public   static  partial  class  Ops {
    
//  为类型注册属性注入器 
     public   static   void  RegisterAttributesInjectorForType(Type ty, IAttributesInjector attrInjector,  bool  prepend) {
        
//  需要转换到 ReflectedType
        ReflectedType rty  =  GetDynamicTypeFromType(ty)  as  ReflectedType;

        
if  (rty  ==   null ) {
            
throw   new  Exception( " failed to obtain ReflectedType from Type " );
        }

        rty.RegisterAttributesInjector(attrInjector, prepend);
    }
}

这里,首先 ty 这个 CLR 类型通过 GetDynamicTypeFromType() 方法调用,得到了一个 DynamicType. 然后又转化为 ReflectedType. 至于这几种类型分别是什么含义,我打算留待以后在 IP 源码分析系列中详述。

关键是,GetDynamicTypeFromType() 这个方法从 Ops 的静态变量 dynamicTypes 中,以 ty 为键,取得了一个对应的 DynamicType. 这里 dynamicTypes 是一个类型字典,或者说 TypeCache.
Ops 类内部封装了很多的静态方法,它的调用者,大多是 IronPython 通过 Emit 的方式动态产生的代码,Ops 类就是为这些生成的代码调用低层次操作提供了便利。

那么这个类型字典是什么意思呢?根据我目前的理解,IP 的引擎内部如果需要调用到外面传进来的 CLR 类型,实际上会用其对应的 DynamicType 来调用。这时候,就可以在 DynamicType 中做各种手脚以实现动态性了。比如本文讨论的属性注入器。同时,因为这个类型字典是 static 的,所以就相当于类型(DynamicType) 的 Cache. 在以后需要进行相关的操作时,引擎会首先检查这个 Cache 里是否已有对应于某个 CLR Type 的 DynamicType,如果有就直接取用。而这个时候,该 DynamicType 已经被我们修改过了(添加了属性注入器),这就是注册一次以后一直能够起作用的原因。IP for ASP.NET 把这个注册的初始化动作放到 HttpModule 的静态构造器里面来做,也可以说是非常的巧妙。

OK, 从上面的代码里我们看到,原先要求注册的 CLR 类型变成了其对应的 DynamicType,进而转换为 DynamicType 的子类 ReflectedType. 最后,调用 ReflectedType 的方法完成了属性注入器的注册工作。代码如下:

[PythonType( typeof (DynamicType))]
public   class  ReflectedType : DynamicType, IContextAwareMember {
    
//  前置属性注入器
     private  IAttributesInjector prependedAttrs;

    
//  后置属性注入器
     private  IAttributesInjector appendedAttrs;

    
//  注册属性注入器   
    
//  参数 prepend 表示是不是前置的注入器(否则作为后置处理) 
     internal   void  RegisterAttributesInjector(IAttributesInjector attrInjector,  bool  prepend) {
        
//  这里根据情况把 attrInjector 赋值给 prependedAttrs(前置)或 appendedAttrs(后置)
        
//
    }

    
    
//  覆盖获取属性的方法   
     public   override   bool  TryGetAttr(ICallerContext context,  object  self, SymbolId name,  out   object  ret) {
        
//  前置属性注入器(prepended):
         if  (prependedAttrs  !=   null   &&  prependedAttrs.TryGetAttr(self, name,  out  ret)) {
            
return   true ;
        }
        
//  再尝试基类定义的方法来获取属性
         if  (TryBaseGetAttr(context, self, name,  out  ret)) 
            
return   true ;

        
//  后置属性注入器(appended):
         if  (appendedAttrs  !=   null   &&  appendedAttrs.TryGetAttr(self, name,  out  ret)) {
            
return   true ;
        }

        
return   false ;
    }


    
//  获取所有属性名称的集合   
     public   override  List GetAttrNames(ICallerContext context,  object  self) {
        List ret;

        
//  尝试使用前置属性注入器获取其产生的属性名称列表
         if  (prependedAttrs  !=   null ) {               
            ret 
=  prependedAttrs.GetAttrNames(self);

            
//  无重复的,不锁定的,把基类中获取的属性名称信息添加到 ret 列表中
            ret.AppendListNoLockNoDups( base .GetAttrNames(context, self));
        } 
else  {
            ret 
=   base .GetAttrNames(context, self);
        }

        
//  尝试用后置属性注入器获取属性名称列表,结果也合并到 ret 中。
         if  (appendedAttrs  !=   null ) {
            ret.AppendListNoLockNoDups(appendedAttrs.GetAttrNames(self));
        }

        
return  ret;
    }
}

这里可以看到属性注入器不仅可以有前置的,还可以有后置的。和一些 AOP 的实现有点类似。
ReflectedType 类通过添加 IAttributesInjector 的两个外部实现,覆盖父类 DynamicType 的属性获取方式,实现了注入器的机制。这里利用的是 策略模式(Strategy Pattern).  IAttributesInjector 接口定义的就是一种注入策略。

看完了属性注入器的实现,我们可以发现,这个机制并不限于 IP for ASP.NET 的实现里才能用。在我们自己的程序有需要的情况下,可以很方便的模拟这个例子,实现自己的属性注入器,因为注入的功能是由 IP 引擎提供的,这也是 IP 作为动态语言的特征体现之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值