65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 Castle DynamicProxy

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2010年5月8日

CPOL

7分钟阅读

viewsIcon

26874

downloadIcon

200

演示如何通过使用 Reflection.Emit 来扩展此框架生成的代理。

目录

引言

在我上一篇文章中,我展示了如何使用自定义类型描述符“扩展”一个框架。不幸的是,使用此机制所做的修改仅通过 TypeDescriptor 的检查可见,因为它们不是真实的。在本文中,我将展示如何使用 Castle MicroKernel/DynamicProxy 通过控制反转真正地扩展一个框架。

Castle DynamicProxy 的创建有一个非常特定的任务:通过方法拦截实现面向切面的编程。它的开发者并没有创建它来允许第三方扩展。然而,Castle Project 库的设计非常出色且松耦合,在下载并检查其源代码后,我很容易就找到了一些“非预期的”扩展点,它们不仅允许我修改生成的代理,还能将这些新功能与 Castle Microkernel/Windsor 集成。

本文将涵盖以下主题

  • 扩展 Castle DynamicProxy,允许插入贡献者(ITypeContributor 对象)来修改动态生成的类型。
  • 将此功能与 Microkernel/Windsor 集成,允许消费者在 ComponentModel 中指定自己的 ITypeContributor
  • 构建一个 Microkernel 'facility',这是一个主要用于扩展或将 Microkernel 与另一个框架集成的类。为此,我将展示一个将 PresentationModel 框架与 Microkernel 集成的 facility。
  • 展示这些修改如何影响我上一篇文章中的演示应用程序。

目标受众

本文适合对控制反转感兴趣的人,或者对运行时扩展现有框架感兴趣的人。我还会展示 Microkernel/Windsor 的更高级用法,因此它可能对该容器的用户来说很有趣。期望读者对创建代理有一定的了解,无论是使用 Castle 的库还是任何其他框架。 这里有一个很棒的教程,用于理解代理是什么以及如何使用 Castle DynamicProxy。

扩展动态代理

要手动使用 Castle 的 DynamicProxy 创建代理,确实需要实例化 ProxyGenerator 类,然后调用它的一个方法,并可选地传递选项或拦截器。

ProxyGenerator generator = new ProxyGenerator();
var proxy = generator.CreateClassProxy<SomeType<(interceptor1, interceptor2);

ProxyGenerator 类将任务委派给一个内部服务 IProxyBuilder,这就是为什么它有一个构造函数重载。

ProxyGenerator generator = new ProxyGenerator(someIProxyBuilderObject);

如果未传递 IProxyBuilder,它将使用默认实现 DefaultProxyBuilderDefaultProxyBuilder 本身只会选择一个合适的类来构建特定类型的代理(有接口/类代理,带或不带目标)。在查看了每个生成器类的代码后,我注意到在某个时候,它会将正在发出的动态类型传递给 'ITypeContributor' 对象进行处理。这是它的契约。

public interface ITypeContributor
{
    void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model);
    void Generate(ClassEmitter @class, ProxyGenerationOptions options);
}

ClassEmitterTypeBuilder 属性中公开 System.Reflection.Emit.TypeBuilder。因此,每个 ITypeContributor 都可以对动态类型进行任何修改。问题是,没有内置的方法来插入自定义 ITypeContributor 进行处理;贡献者集合是在每个生成器类中的一个方法内部创建的。这是 ClassProxyGenerator 类内部该方法的签名。

protected virtual IEnumerable<Type> GetTypeImplementerMapping(Type[] interfaces, 
   out IEnumerable<ITypeContributor> contributors, INamingScope namingScope);

它通过 out 参数返回贡献者。幸运的是,这是一个虚方法,因此如果扩展该类,就可以插入更多贡献者。由于每个专门的代理生成类都有自己的方法版本,因此所有这些都需要被扩展。

protected override IEnumerable<Type> GetTypeImplementerMapping(Type[] interfaces, 
        out IEnumerable<ITypeContributor> contributors, INamingScope namingScope)
{
    IEnumerable<ITypeContributor> contr;
    var ret = base.GetTypeImplementerMapping(interfaces, out contr, namingScope);
    var list = contr.ToList();
    list.AddRange(_proxyBuilder.Contributors);
    contributors = list;
    return ret;
}

_proxyBuilder 字段是对我必须实现的自定义 ProxyBuilder 的引用。不幸的是,DefaultProxyBuilder 类不提供任何扩展点,所以我不得不从头开始构建一个。基本上,我复制了 DefaultProxyBuilder 的代码,并得到了这个。

public class ExtensibleProxyBuilder : IProxyBuilder
{
    private ILogger _logger = NullLogger.Instance;
    private readonly ModuleScope _scope;

    public ExtensibleProxyBuilder(params ITypeContributor[] contributors)
        : this(new ModuleScope(), contributors)
    {
       
    }

    public ExtensibleProxyBuilder(ModuleScope scope, 
           params ITypeContributor[] contributors)
    {
        this._scope = scope;
        foreach (var item in contributors)
        {
            _contributors.Add(item);
        }
    }   

    private List<ITypeContributor> _contributors = new List<ITypeContributor>();
    public List<ITypeContributor> Contributors
    {
        get { return _contributors; }
    }

    public Type CreateClassProxyType(Type classToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(classToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleClassProxyGenerator(_scope, 
                            classToProxy, this) { Logger = _logger };
        return generator.GenerateCode(additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, Type targetType, 
           ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleInterfaceProxyWithTargetGenerator(
                            _scope, interfaceToProxy, this) { Logger = _logger };
        return generator.GenerateCode(targetType, additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new ExtensibleInterfaceProxyWithTargetInterfaceGenerator(
                            _scope, interfaceToProxy, this) { Logger = _logger };
        return generator.GenerateCode(interfaceToProxy, additionalInterfacesToProxy, options);
    }

    public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, 
           Type[] additionalInterfacesToProxy, ProxyGenerationOptions options)
    {
        AssertValidType(interfaceToProxy);
        AssertValidTypes(additionalInterfacesToProxy);

        var generator = new InterfaceProxyWithoutTargetGenerator(_scope, 
                              interfaceToProxy) { Logger = _logger };
        return generator.GenerateCode(typeof(object), additionalInterfacesToProxy, options);
    }
}

这个类还有其他方法,但主要的是这些。这基本上是 DefaultProxyBuilder 的副本,除了它有一个列表用于插入自定义用户定义的 ITypeContributor,并创建了我扩展的专门代理生成类。现在修改代理很容易了。

ExtensibleProxyBuilder builder = new ExtensibleProxyBuilder(tcontributor1, tcontributor2);
ProxyGenerator generator = new ProxyGenerator(builder);

与 Microkernel 集成

默认情况下,Microkernel 不会为其组件创建代理,即使拦截器已添加到 ComponentModel 中。Microkernel/Windsor 中有一个 IProxyFactory 类型的属性,负责在需要时创建代理;这是它的契约。

public interface IProxyFactory
{
    void AddInterceptorSelector(IModelInterceptorsSelector selector);
    object Create(IKernel kernel, object instance, ComponentModel model, 
                  CreationContext context, params object[] constructorArguments);
    bool RequiresTargetInstance(IKernel kernel, ComponentModel model);
    bool ShouldCreateProxy(ComponentModel model);
}

Microkernel 和 Windsor 之间的区别在于,Microkernel 有该接口的虚拟实现,在 ShouldCreateProxy 方法中始终返回 false。要将这个新的 ExtensibleProxyBuilder 与 Microkernel/Windsor 集成,我们需要创建一个新的 IProxyFactoryDefaultProxyFactory 只有一个允许我们自定义代理对象的方法,但由于类型已经创建,这样做没有任何用处。而且由于它不将 ShouldCreateProxy 公开为 virtual,唯一的选择是从头开始实现 IProxyFactory。同样,这是一个复制粘贴操作,在 CreateShouldCreateProxy 方法中进行了一些微小但重要的修改。

public object Create(IKernel kernel, object target, ComponentModel model, 
       CreationContext context, params object[] constructorArguments)
{
    ExtensibleProxyBuilder proxyBuilder;
    if (model.ExtendedProperties.Contains(ExtensibleProxyConstants.ProxyTypeContributorsKey))
        proxyBuilder = new ExtensibleProxyBuilder(_moduleScope, 
            (model.ExtendedProperties[ExtensibleProxyConstants.ProxyTypeContributorsKey] 
             as List<ITypeContributor>).ToArray());
    else
        proxyBuilder = new ExtensibleProxyBuilder(_moduleScope);

    ProxyGenerator generator = new ProxyGenerator(proxyBuilder);
    //In the 'DefaultProxyFactory' the 'ProxyGenerator' is a field
    //but since I will pass diferent contributors based on the ComponentModel,
    //it must be created everytime(A little more costly, but not enough for 
    //the user to notice, and definitly worth it)
    //following this is the code from the original proxy factory.
}

public bool ShouldCreateProxy(ComponentModel model)
{
    if (model.ExtendedProperties.Contains(ExtensibleProxyConstants.ProxyTypeContributorsKey))
        return true;
        
    //Same here, only an additional check to see if a proxy should be 
    //created, the rest is just like the original
}

这就是将这个可扩展的 DynamicProxy 与 Microkernel 集成所需的所有内容。现在我将展示如何将其应用于我上一篇文章中的示例。

将 PresentationModel 框架与 WPF 集成

在我上一个示例中,我创建了一个包含在 Windsor 中注册组件的所有代码的配置程序集。其中大部分代码都与将 PresentationModel 框架(它独立于任何 UI 引擎)集成到 WPF 相关。这次,我将所有这些配置移到一个包含专门 facility(Microkernel 扩展)的程序集中。区别在于,在此 facility 中,我使用实现 IContributeComponentModelConstruction 的类来修改组件模型,因为它被注册。我还删除了所有自定义类型描述符,并将它们替换为 ITypeContributor 接口的实现。这是一个 IContributeComponentModelConstruction 示例及其对应的 ITypeContributor

public class IsWorkingPropertyComponentContributor : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (typeof(IPresentationModel).IsAssignableFrom(model.Implementation))
        {
            var contributors = model.GetTypeContributors();
            contributors.Add(new NotificatingPropertyTypeContributor(
                             "IsWorking", typeof(bool)));
        }
    }
}

public class NotificatingPropertyTypeContributor : ITypeContributor
{
    public NotificatingPropertyTypeContributor(string propertyName, Type propertyType)
    {
        _propertyName = propertyName;
        _propertyType = propertyType;
    }

    string _propertyName;
    Type _propertyType;

    public void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
    {

    }

    public void Generate(ClassEmitter @class, ProxyGenerationOptions options)
    {
        GenerateSimpleProperty(@class.TypeBuilder);
    
    }

    protected virtual void GenerateSimpleProperty(TypeBuilder typeBuilder)
    {
        var fieldBuilder = typeBuilder.DefineField(
            string.Format("_{0}", _propertyName),
            _propertyType,
            FieldAttributes.Private);
        var propertyBuilder = typeBuilder.DefineProperty(
            _propertyName,
            PropertyAttributes.None,
            _propertyType,
            Type.EmptyTypes);
      
        var getterBuilder = typeBuilder.DefineMethod(
            string.Format("get_{0}", _propertyName),
            MethodAttributes.Public | MethodAttributes.HideBySig | 
            MethodAttributes.SpecialName,
            _propertyType,
            Type.EmptyTypes);
        var getterGenerator = getterBuilder.GetILGenerator();
        getterGenerator.Emit(OpCodes.Ldarg_0);
        getterGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
        getterGenerator.Emit(OpCodes.Ret);

        var setterBuilder = typeBuilder.DefineMethod(
            string.Format("set_{0}", _propertyName),
            MethodAttributes.Public | MethodAttributes.HideBySig | 
            MethodAttributes.SpecialName,
            null,
            new Type[] { _propertyType });
        var setterGenerator = setterBuilder.GetILGenerator();
        Label returnInstruction = setterGenerator.DefineLabel();
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
        setterGenerator.Emit(OpCodes.Ldarg_1);
        setterGenerator.Emit(OpCodes.Ceq);
        setterGenerator.Emit(OpCodes.Brtrue, returnInstruction);
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldarg_1);
        setterGenerator.Emit(OpCodes.Stfld, fieldBuilder);
        setterGenerator.Emit(OpCodes.Ldarg_0);
        setterGenerator.Emit(OpCodes.Ldstr, _propertyName);            
        setterGenerator.Emit(OpCodes.Callvirt, 
           typeBuilder.BaseType.GetMethod("OnPropertyChanged", 
           BindingFlags.NonPublic | BindingFlags.Instance));
        setterGenerator.MarkLabel(returnInstruction);
        setterGenerator.Emit(OpCodes.Ret);
    
        propertyBuilder.SetGetMethod(getterBuilder);
        propertyBuilder.SetSetMethod(setterBuilder);
    }
}

NotificatingPropertyTypeContributor 类允许用户添加一个简单的属性,该属性会在 PresentationModel 上引发更改通知。有些人可能不习惯对堆栈机进行编程,但请注意,在 .NET 4.0 中,您可以使用表达式树编写动态方法。我在 CIL 中这样做是为了使此示例与 .NET 3.5 兼容。IsWorking 贡献者创建了一个具有类似行为的属性。

private bool _isWorking;
public virtual bool IsWorking
{
    get { return _isWorking; }
    set
    {
        if (_isWorking == value)
            return;
        _isWorking = value;
        OnPropertyChanged("IsWorking");
    }
}

CallPropertyTypeContributor 向 PresentationModels 添加了此属性。

private MethodCallCommand _call
public MethodCallCommand Call
{
    get
    {
        if (_call == null)
            _call = new MethodCallCommand(this);
        return _call;
    }
}

所有拦截器和 ITypeContributor 实现都将通过 IContributeComponentModelConstruction 实现添加到 ComponentModel 中,而这些又将添加到 Microkernel facility 中。这个 facility 可在任何 PresentationModel/WPF 项目中重复使用。

public class PresentationModelWpfFacility : AbstractFacility
{
    protected override void Init()
    {
        if (!typeof(ExtensibleProxyFactory).IsAssignableFrom(
                    Kernel.ProxyFactory.GetType()))
            Kernel.ProxyFactory = new ExtensibleProxyFactory();

        HandleContributors();
        RegisterComponents();
    }

    protected void HandleContributors()
    {
        var propertyDIContributor = 
          Kernel.ComponentModelBuilder.Contributors.OfType<
          PropertiesDependenciesModelInspector>().Single();
        Kernel.ComponentModelBuilder.RemoveContributor(propertyDIContributor);
        Kernel.ComponentModelBuilder.AddContributor(
               new CallPropertyComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(new 
               EntityViewEncapsulationPropertiesComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(
               new IsWorkingPropertyComponentContributor());
        Kernel.ComponentModelBuilder.AddContributor(
               new AutomaticThreadingComponentContributor());
    }

    protected void RegisterComponents()
    {
        Kernel.AddComponentInstance("presentationmodel.proxyoptions", 
               new ProxyOptions() { Hook = new ThreadingProxyHook() });
        Kernel.Register
            (
            Component.For(typeof(ObservableCollection<>), 
              typeof(ICollection<>)).LifeStyle.Is(LifestyleType.Transient),
            Component.For<WpfDialogSystem, IDialogSystem>(),
            Component.For<CustomActionPresentationModel, 
              ICustomActionPresentationModel>().LifeStyle.Is(LifestyleType.Transient),
            Component.For<DispatchInterceptor>(),
            Component.For<BackgroundWorkInterceptor>()
            );
    }
}

我还对应用程序核心和框架进行了更改。首先,我删除了 EntityViewPresentationModel<T> 类。现在只有一个 IEntityViewPresentationModel<T>> 接口。

public interface IEntityViewPresentationModel<T> : ISelectableViewPresentationModel
       where T : class, new()
{
    T Entity { get; set; }
}

该接口有一个简单的实现,并且一个 PresentationModel 现在可以多次实现该接口,如果它希望显示多个实体类型的属性。这就是为什么我对 EncapsulatesPropertyAttribute 进行了更改。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class EncapsulatesPropertyAttribute : Attribute
{
    public EncapsulatesPropertyAttribute(Type entityType, string propertyName)
    {
        _entityType = entityType;
        _propertyName = propertyName;
    }        

    private string _propertyName;
    public string PropertyName
    {
        get { return _propertyName; }
        set { _propertyName = value; }
    }      

    private Type _entityType;
    public Type EntityType
    {
        get { return _entityType; }
        set { _entityType = value; }
    }
}

这允许 PresentationModel 同时封装许多实体的属性。这是 EntityViewEncapsulationPropertiesComponentContributor 的代码。

public class EntityViewEncapsulationPropertiesComponentContributor : 
       IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        var contractDefiniton = typeof(IEntityViewPresentationModel<>);
        var entityViews = model.Implementation.GetInterfaces()
             .Where(t => t.IsGenericType && 
                    t.GetGenericTypeDefinition() == contractDefiniton);
            
        if (entityViews.Count() > 0) 
        {
            foreach (var entityView in entityViews)
            {
                var entityType = entityView.GetGenericArguments()[0];
                var contributors = model.GetTypeContributors();                    
                var propertiesToBeEncapsulated =
                    model.Implementation.GetCustomAttributes(
                    typeof(EncapsulatesPropertyAttribute), true)
                    .OfType<EncapsulatesPropertyAttribute>()
                    .Where(attr => attr.EntityType == entityType);

                foreach (var encapsulatedProperty in propertiesToBeEncapsulated)
                {                      
                    //checking if the property exits in the type
                    PropertyInfo pi = 
                      entityType.GetProperty(encapsulatedProperty.PropertyName);
                    if (pi == null)
                        continue;
                    var typeContributor =
                        new EntityViewEncapsulationPropertyTypeContributor(
                            pi.Name,
                            entityType,
                            pi.PropertyType,
                            model.Implementation);
                    contributors.Add(typeContributor);
                }
            }          
        }
    }
}

简单来说,这将查找 PresentationModel 实现的每一个接口,该接口是 IEntityViewPresentationModel<> 泛型接口定义的构造泛型接口。对于这些接口中的每一个,它将构造属性,这些属性封装由 EncapsulatesPropertyAttribute 指定的、与泛型接口的类型参数关联的属性。现在,这是新的配置类。

public class ConfigurationManager
{
    internal static DefaultKernel _microkernel = new DefaultKernel();

    public static void Configure()
    {
        AppDomain.CurrentDomain.SetData("servicelocator", _microkernel);
        _microkernel.AddFacility<PresentationModelWpfFacility>();

        
        _microkernel.Register
            (
            Component.For<MainViewPresentationModel, 
              IMainViewPresentationModel>().
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For<ProductsViewPresentationModel, 
              IEntityCollectionViewPresentationModel<Product>>().
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For(typeof(DummyDao<>), typeof(IDao<>)).
              LifeStyle.Is(LifestyleType.Singleton),
            Component.For(typeof(List<>), typeof(IList<>)).
              LifeStyle.Is(LifestyleType.Transient),
            Component.For<ProductEditViewPresentationModel, 
              IEntityViewPresentationModel<Product>>().
              LifeStyle.Is(LifestyleType.Transient)
            );
        InsertStubData();
    }
}

是不是简单多了?请注意,我用 Microkernel 替换了 Windsor。虽然 Windsor 的功能比 Microkernel 多,但 DefaultProxyFactory 是唯一被使用的 Windsor 独有功能。由于我创建了一个与 Windsor 无关的新 IProxyFactory 实现,我宁愿不创建不必要的依赖项。

结论

正如本文所示,通过动态代理和控制反转,可以集成彼此不知情的框架。还可以通过动态生成代码来大大减少重复代码量。我在这里展示的只是程序员通过运行时构建类型可以实现的可能性的 1%。例如,可以为每个 PresentationModel 代理生成专门的 ICommand 实现作为嵌套类型(这些 ICommand 将根据 PresentationModel 中包含的方法创建)。

希望您喜欢我在这里展示的内容;请留下您的评论/建议,以便我改进我未来的文章。

© . All rights reserved.