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 作为动态语言的特征体现之一。

posted on 2006-12-07 16:46  NeilChen  阅读(2727)  评论(8编辑  收藏  举报

导航