Leo Zhang

A simple man with my own ideal

浅析如何在ObjectBuilder2中用动态方法进行构造器注入

一、前言

       在我看来,OB2是一个用来构建和管理对象的开放性的框架,我们可以根据自己的需求去扩展它,例如扩展它可以实现依赖注入(MSUnity)。我认为OB2最大的亮点之一是在提供了对象创建框架的同时能够管理对象以及对象之间的依赖关系,控制对象构建和销毁过程,这样对象的创建就不是直接去使用new而对象的销毁也不仅仅只靠GC了。要说OB2所使用的设计模式,我觉得可以认为是大量使用策略(Strategy)模式并辅以责任链模式,通过责任链组织对象创建或销毁的次序及步骤。

二、关键组成部分

1Locator

       利用一个Dictionary对创建的对象建立<键,值>对,之后通过key可以找到相应的对象,需要注意的是,这里的值是通过System.WeakReference包装过的短弱引用对象,之所以这样做是想在出现内存压力或其他情况时允许Locator中的对象被GC回收,而强调使用短弱引用是保证出现内存压力时,对象最晚会在进行第一次finalize后被回收,注意GCfinalize使用两个不同的工作线程,GC线程被调用会触发finalize线程开始工作,但GC线程完成收集工作并不意味着finalize线程工作结束,这就意味着对象在GC线程开始工作到finalize线程释放目标对象之前,目标对象都可以被激活,而这里不用强弱引用恐怕是因为强弱引用对象会在第一次finalize和第二次finalize之间仍然存在,如果这时候目标对象被激活可能导致对象状态错误(因为也许对象中某些部分已经被回收)

2LifetimeContainer

       以一个只读的List<Object>结构存储对象,管理对象的生命周期。不管是值类型还是引用类型,都可以被加入到容器中,不管是同一个类型的同一个对象还是同一个类型的不同对象都可以被同时加入容器,可以对容器中所有实现IDisposable接口的对象进行统一的释放。

3StrategyPolicy

       Strategy,是对算法的封装,OB2中大量使用了策略模式,我们可以将对象的创建、销毁过程抽象成算法,利用Strategy进行封装从而实现复用和倒置通用算法与具体实现之间的依赖关系。

       Policy,通过传递某些信息辅助Strategy,所谓传递的信息是多种多样的,只要Strategy需要就可以通过Policy传入,因此有相当大的灵活性,当然Strategy也可以不使用Policy,这就意味着该Strategy不需要通过外部传递给它信息就能实现算法。

       StrategyPolicy相辅相成,为我们创建对象提供了很大的灵活度。

4BuilderContext

       构建上下文,我们的Strategy是以流水线的方式执行的,那么对上下文环境进行封装,为其提供一个统一的状态存储和共享的环境是有必要的,这样每个Strategy所针对的上下文环境是确定的,可以清晰的看到这些变化,也就可以随时截取这些变化,我们总是习惯将变化的东西封装起来,当然不同的上下文之间又可以起到相互隔离的作用,避免有意或无意的干扰,让对象只知道它应该知道的信息。最后,在构建上下文中存储了最终生成的对象。

三、利用动态方法进行构造器注入

1、基本思路

       确定待构建类型TypeToBuild

TypeToBuild建立一个动态方法(Dynamic Method),该动态方法的方法体的构建利用反射发出(Reflection Emit)实现(使用MSIL),大致过程是:找到TypeToBuild的构造函数(优先查找打了构造器注入标志的构造函数),利用特定Strategy+Policy对其参数进行解析并将解析结果压入Evaluation Stack,最后调用相应指令生成对象;

大家可以看到,这里出现了一个新的变化点,所以需要建立一个与构造器注入相关的上下文环境,这个环境封装如下内容:待构建类型TypeToBuild、为生成TypeToBuild所需要的动态方法(System. DynamicMethod)、用来在内存中生成MSIL代码的ILGenerator对象、局部变量,如图:

 

 

由于构建过程中在不同策略间负责传递信息的是IBuilderContext(构建上下文),因此,自然而然的在构建我们的动态方法时需要的信息也是从IBuilderContext中获取,所以我们要构建的用于生成TypeToBuild类型对象的方法原型类似于这样:

Void BuildUp_*** (IBuilderContext context)

最终,生成的BuildUp_***方法被调用后即可生成相应对象。

2、相关StrategyPolicy分析

Strategy

BuildPlanStrategy,用于构建目标类型的对象

       它会在当前上下文中查找是否有构建对象的计划(继承了IBuildPlanPolicy接口的Policy),如果没有则在当前上下文中获取继承了IBuildPlanCreatorPolicy类型的Policy来生成一个构建计划,最后调用构建计划的BuildUp方法生成对象。如图所示:

 

DynamicMethodConstructorStrategy,用于构建与构造器注入相关的上下文环境,包括动态方法的方法体,这里给出构建动态方法的MSIL伪码 

MSIL伪码
.method public hidebysig virtual instance void BuildUp_"methodname" (IBuilderContext context) cil managed
{
    
.maxstack 3
    
.locals init(
            [
0class typeToBuild 'existingObjectLocal'
            [
1class [mscorlib]System.String 'currentParameterName')

    
L_0000: ldarg.0
    
L_0001: callvirt 'Existing'
    
L_0002: cast class typeToBuild
    
L_0003: stloc.0 //完成获取方法所在类型的Type操作并存在局部变量existingObjectLocal中,
                    //Evaluation Stack为空
     
    
L_0004: ldloc.0  //将要构建的Type加载到局部变量区中
    L_001f: ldnull  

    
L_0005: ceq
    
L_0006: brfalse existingObjectNotNull  //待构建类型为null则返回
    L_0007: ldarg.0
    
L_0008: call 'ThrowForAttemptingToConstructInterface' //判断当前类型是否为接口
                                                          //是,则抛出异常
    
L_0009: BeginExceptionBlock

    
L_002e: nop
    
L_002f: ldstr 'parameters[i].Name' //获取当前参数名称并存储于局部变量
                                       //currentParameterName中
    L_0010: stloc.1

    
L_0012: ldarg.0  //将上下文实例压入Evaluation Stack
    L_0013: callvirt 'Policies' //从上下文获取PolicyList
    L_0014: ldtoken 'typeof (IDependencyResolverPolicy)'//从MetaData
                                                        //获取IDependencyResolverPolicy
                                                        //类型的token

    
L_0015: call System.Type.'GetTypeFromHandle' //执行call指令,依据句柄获取类型,
    //返回值为类型,为什么要用这种方式?因为Policy在存的时候以<IDependencyResolverPolicy,Key>
    //方式存储,所以需要去metaData中找到IDependencyResolverPolicy的Type

    
L_0016: ldstr 'key with GUID' //获取当前参数的BuildKey用来取出相应的Policy
    L_0017: callvirt 'IPolicyList.Get' //调用其Get方法取出相应Policy
    L_0018: cast class 'typeof(IDependencyResolverPolicy)'//完成Policy向
                                    //IDependencyResolverPolicy的类型转换

    
L_0019: ldarg.0 //向Evaluation Stack中压入上下文
    L_0020: callvirt 'Resolve' //调用找出的DependencyResolverPolicy的Resolve方法
                               //以生成参数并将参数压入Evaluation Stack

    
L_0021: brtrue L_002e //构造函数的所有参数都解析完毕则跳出


    
L_0022: ldnull
    
L_0023: stloc.1 //清空局部变量currentParameterName先

    
L_0024: newobj 'selectedCtor.Constructor' //其实就是将Evaluation Stack中参数先弹出,
                                              //然后依据ConstructorInfo信息调用待构建
                                    //类型的构造函数,并将生成的对象压入Evaluation Stack
                                              
    L_0025: stloc.0 //将生成的对象保存到局部变量区中,保证Evaluation Stack为空

    
L_0026: BeginCatchBlock //传入类型为Exception

    
exceptionOccuredInResolution:

    
L_0027: ldloc.1
    
L_0028: ldnull
    
L_0029: ceq
    
L_0030: brfalse exceptionOccuredInResolution
    
L_0031: rethrow     //处理完异常之后再将其抛出

    
L_0032: ldloc.1
    
L_0033: ldstr 'signatureString' 
    
L_0034: ldarg.0
    
L_0035: call 'throwForResolutionFailed'

    
L_0036: EndExceptionBlock

    
existingObjectNotNull:
}

 

构建流程类似如下:

 

 

 

Policy

DefaultDynamicBuilderMethodCreatorPolicy,用来生成动态方法原型:

       Void BuildUp_*** (IBuilderContext context)

ConstructorSelectorPolicy,辅助DynamicMethodConstructorStrategy,用于查找类型中打了构造器注入标记的构造函数,并将其参数进行解析,对不同的参数如何解析是需要我们自己扩展的。

DynamicMethodBuildPlanCreatorPolicy,用来生成构建计划,利用DefaultDynamicBuilderMethodCreatorPolicyDynamicMethodConstructorStrategy构建BuildUp_***的动态方法并将结果保存于动态方法的上下文环境:

DynamicBuildPlanGenerationContext,最后利用动态方法的上下文环境生成该方法的委托并作为参数生成构建计划。

3、实例分析

需求:模拟建立一个线程池ThreadPool,建立对象时需要传入线程池的名字和允许最大线程数,使用OB2实现对象构建。

1ThreadPool的原型如下,其构造函数有两个参数,分别为String类型和Int32类型:

 

ThreadPool
    public class ThreadPool
    {
        
private String poolName = String.Empty;
        
private Int32 maxThread = 10;

        
//[InjectionThreadPoll]
        public ThreadPool()
        {
            Console.ForegroundColor 
= ConsoleColor.Red;
            Console.WriteLine(
"The default constructor is called...");
        }

        [InjectionThreadPoll]
        
public ThreadPool(String name, Int32 num)
        {
            
this.poolName = name;
            
this.maxThread = num;
            OutPut();
        }

        
private void OutPut()
        {
            
this.SetForegroundColor(ConsoleColor.White);
            Console.Write(
"The thread pool's name is ");
            
this.SetForegroundColor(ConsoleColor.Red);
            Console.WriteLine(
this.poolName);
            
this.SetForegroundColor(ConsoleColor.White);
            Console.Write(
"The number of threads in a thread pool should not exceed ");
            
this.SetForegroundColor(ConsoleColor.Red);
            Console.WriteLine(
this.maxThread);
        }

        
private void SetForegroundColor(ConsoleColor color)
        {
            Console.ForegroundColor 
= color;
     }

 

2、需要使用的StrategyPolicy如下:

OB2自定义的

BuildPlanStrategyDynamicMethodConstructorStrategyDefaultDynamicBuilderMethodCreatorPolicyConstructorSelectorPolicyDynamicMethodBuildPlanCreatorPolicy

需要自己扩展的

这部分主要集中到参数的解析上,我的做法是建立一个ParametersResolveStrategy,专门用来解析参数,此时传来的上下文中包含参数的类型,利用参数类型去寻找相应的PolicyParametersResolveStrategy+Policy可以得到参数实例。

参数通过配置文件读出,Policy的实现如下:

  

ParameterChooserPolicy
    public interface IParameterChooserPolicy : IBuilderPolicy
    {
        Object GetValue();
    }

    
public class ParameterChooserPolicy<TParameterType> : IParameterChooserPolicy
    {
        
public virtual Object GetValue()
        {
            Object obj 
= ConfigurationManager.AppSettings[GetNameOfType(typeof(TParameterType))];
            
return Convert.ChangeType(obj,typeof(TParameterType));
        }

        
private String GetNameOfType(Type type)
        {
            
return type.ToString();
        }
}

 

3、建立一个ThreadPoolBuilder类,用来配置StrategyPolicy,实现如下:

 

ThreadPoolBuilder
    public class ThreadPoolBuilder<TTypeToBuild> 
        
where TTypeToBuild : class
    {
        
private NamedTypeBuildKey key;
        
private StagedStrategyChain<BuilderStage> strategies = new StagedStrategyChain<BuilderStage>();

        
private IReadWriteLocator locator = null;
        
private ILifetimeContainer lifetime = null;

        
public ThreadPoolBuilder()
        {
            key 
= new NamedTypeBuildKey(typeof(TTypeToBuild));
            
this.strategies.Add(new BuildPlanStrategy(), BuilderStage.PreCreation);
            
this.strategies.Add(new ParametersResolveStrategy(), BuilderStage.Creation);
            
this.SetInnerStrategies();
        }

        
public IReadWriteLocator Locator
        {
            
get
            {
                
return this.locator;
            }

            
set
            {
                
this.locator = value;
            }
        }

        
public ILifetimeContainer Liftime
        {
            
get
            {
                
return this.lifetime;
            }

            
set
            {
                
this.lifetime = value;
            }
        }

        
public virtual IStagedStrategyChain SetInnerStrategies()
        {
            StagedStrategyChain
<BuilderStage> innerStrategies = new StagedStrategyChain<BuilderStage>();
            innerStrategies.Add(
new DynamicMethodConstructorStrategy(), BuilderStage.PreCreation);
            
return innerStrategies;
        }

        
public virtual IPolicyList GetPolicies()
        {
            PolicyList policies 
= new PolicyList();
            policies.Set
<IBuildPlanCreatorPolicy>(new DynamicMethodBuildPlanCreatorPolicy(SetInnerStrategies()), key);
            policies.Set
<IDynamicBuilderMethodCreatorPolicy>(new DefaultDynamicBuilderMethodCreatorPolicy(), key);
            policies.Set
<IConstructorSelectorPolicy>(new ConstructorSelectorPolicy<InjectionThreadPollAttribute>(), key);

            policies.Set
<IParameterChooserPolicy>(new ParameterChooserPolicy<String>(), typeof(String));
            policies.Set
<IParameterChooserPolicy>(new ParameterChooserPolicy<Int32>(), typeof(Int32));

            
return policies;
        }

        
public TTypeToBuild BuildUp()
        {
            
return new Builder().BuildUp<TTypeToBuild>(
                
this.locator,
                
this.lifetime,
                
this.GetPolicies(),
                
this.strategies.MakeStrategyChain(),
                key,
                
null);
        }
}

  

  最后,使用的时候这样就可以了: 

ThreadPool t1 = new ThreadPoolBuilder<ThreadPool>().BuildUp();

 

4、总结

    我觉得学习OB2,看懂每行代码不如看懂它的思想,理解为什么这么设计才是关键。顺便说一句,据说OB2中动态方法这一部分是为了Unity而加的,在OB2中用动态方法分别实现了构造器注入、属性注入和方法注入,实现类似,基本上看懂一个就看懂全部了,希望大家多提宝贵意见,看看怎么样才能把OB2用好。

 

本文源代码可以在这里下载

 

 

 

posted on 2010-03-15 18:19  Leo Zhang  阅读(1940)  评论(6编辑  收藏

导航