How does Unity IoC Container create objects

Unity is an open source IoC framework launched by Microsoft P&P. The previous version of Unity was built on a component called ObjectBuild. Readers who are familiar with EnterLib will not be unfamiliar with ObjectBuild. For versions before EnterLib 5.0, ObjectBuild can be said to be the cornerstone of all Application Blocks. ObjectBuild provides an extensible and customizable object creation method. Although Microsoft officially does not link ObjectBuild and IoC together, its essence can be regarded as an IoC framework. In Unity 2.0, Microsoft directly implemented most of the functions of ObjectBuild (actually ObjectBuild's second version, ObjectBuild2) in Unity, and EnterLib was directly built on Unity. This shows the important position of Unity in EnterLib and some other open source frameworks of Microsoft (such as Software Factory).

How the Unity IoC Container creates objects starts from the following aspects.

1. Starting from the Pipeline+Context (Pipeline+Context) mode

If you want to say what design/architecture mode Unity Container adopts, my answer is " Pipeline + Context (Pipeline + Context) mode " (I don't know if it really has such a name). In the chapter introducing Binding in "WCF Technical Analysis (Volume 1)", I once made an analogy to this model: "For example, there is a waterworks that provides drinking water for residents. Purification treatment, and finally transported to residential areas. The process of purification treatment may be like this: the natural water source is drawn into a reservoir to filter impurities first (we call this pool a filter tank); the filtered water flows to Disinfection treatment is carried out in the second pool (we call this pool a disinfection pool); the disinfected water flows to the third pool for water softening treatment (we call this pool a softening pool); finally, the water flows to residents' homes."

When we need to create an infrastructure to perform a series of treatments on a certain element (water that needs to be treated in the example), we can implement the corresponding processing logic (filtering, disinfection and softening in the example) in the corresponding "node "(filter pool, disinfection pool and softening pool in the example). According to the needs (such as water quality), the corresponding nodes are combined in an orderly manner (the difference in water quality determines the difference in the treatment process) to form a pipeline (the entire water treatment pipeline of the waterworks). Since each node has a standard interface, we can reorganize the nodes that make up the pipeline arbitrarily, and also customize nodes for certain needs, so that our "pipeline" can adapt to all processing needs.

We are not unfamiliar with such a design. For example, the runtime of ASP.NET can be regarded as a pipeline for processing HTTP requests composed of several HttpModule, and Binding in WCF is a pipeline for processing Message composed of several channels (Channel). The same design is also reflected in the design of .NET Remoting, BizTalk and other related frameworks and products.

The orderly combination of "nodes" based on corresponding standards constitutes a pipeline, but how do each relatively independent node cooperate accordingly? This requires some contexts to be shared throughout the pipeline. Context is the encapsulation of pipeline processing objects and processing environments. The context object of the ASP.NET runtime pipeline is HttpContext, and the context of the Binding pipeline is BindingContext.

2. UnityContainer is the pipeline of BuildStrategy

As an IoC framework, the ultimate purpose of Unity Container is to dynamically resolve and inject dependencies, and finally provide (create new objects or provide existing objects) an object that meets your requirements. In order to make the entire object provisioning process extensible and customizable, the entire process is designed as a pipeline. Each node of the pipeline is called BuilderStrategy , and they participate in the entire object provision process according to their respective strategies.

In addition to the function of providing objects, Unity Container also provides another opposite function: the recycling of objects. Let's call the two Build-Up and Tear-Down for the time being. Build-Up and Tear-Down use the same processing mechanism.

The picture on the left reflects that the Unity Container is composed of several BuilderStrategy, which is used for the Build-Up and Tear-Down pipeline of the object. Each BuildStrategy has the same interface (this interface is IBuilderStrategy ), and they have four standard methods: PreBuildUp, PostBuildUp, PreTearDown, and PostTearDown. It is not difficult to see from the name that the four methods are used to complete the corresponding operations before/after object creation and before and after object recycling. Specifically, when the pipeline needs to be used to create an object, the PreBuildUp method is first called in the order of the BuilderStrategy in the pipeline, and the PostBuildUp method is called in the reverse order after reaching the bottom of the pipeline.

For each BuilderStrategy that makes up the Unity Container pipeline, they are independent of each other. A BuilderStrategy only needs to complete the corresponding operations based on its own strategy, and does not need to know the existence of other BuilderStrategy. Only in this way can flexible customization of pipelines be realized and scalability be truly realized. But when it comes to real work, they need to share some context with each other to facilitate mutual collaboration. Here, BuilderContext plays such a role. In Unity, BuilderContext implements IBuilderContext , let's take a look at what IBuilderContext defines:

 1: public interface IBuilderContext
 2: { 
          
 3: //Others...
 4: bool BuildComplete { get; set; }
 5: NamedTypeBuildKey BuildKey { get; set; }
 6: NamedTypeBuildKey OriginalBuildKey { get; }
 7: object CurrentOperation { get; set; }
 8: object Existing { get; set; }
 9: }

In the above definition of IBuilderContext , for the sake of simplicity, I deliberately omitted some attributes. In the above attribute list, BuildComplete indicates whether the Build operation is marked as completed. If a BuilderStrategy has completed the Build operation, it can be set to True, so that subsequent BuilderStrategy can perform corresponding operations based on this value (large Part of it will not be used for subsequent Build); BuildKey and OriginalBuildKey are a key representing the current Build operation (expressed by CurrentOperation) with a combination of Type + Name; the Existing attribute indicates the generated object. Generally speaking, BuilderStrategy will generate itself Objects are assigned to this value; Strategies represent the entire BuilderStrategy pipeline.

3. Create the simplest BuilderStrategy

Now let's write the simplest example to see how UnityContainer provides objects with the help of the BuilderStrategy pipeline (you can download the source code here ). Let's create the simplest BuilderStrategy first. Our strategy is to create objects through reflection , so we name the BuilderStrategy ReflectionBuilderStrategy.

 1: public class ReflectionBuilderStrategy:BuilderStrategy
 2: { 
          
 3: public override void PreBuildUp(IBuilderContext context)
 4: { 
          
 5: if (context.BuildComplete || null != context.Existing)
 6: { 
          
 7: return;
 8: }
 9: var value = Activator.CreateInstance(context.BuildKey.Type);
 10: if (null != value)
 11: { 
          
 12: context.Existing = value;
 13: context.BuildComplete = true;
 14: }
 15: }
 16: }

ReflectionBuilderStrategy inherits from the unified base class BuilderStrategy. Since we only need ReflectionBuildStrategy to create objects, here we only need to rewrite the PreBuildUp method. In the PreBuildUp method, if the object to be provided already exists (judged by the Existing property of BuilderContext) or the Build operation has been completed (judged by the BuildComplete property of BuilderContext), it returns directly. Otherwise, get the type of object to be created through the BuildKey attribute of BuilderContext, create the object through the reflection mechanism, assign it to the Existing attribute of BuilderContext, and set BuildComplete to True.

Now that the BuilderStrategy has been created successfully, how to add it to the BuilderStrategy pipeline of UnityContainer? Generally, we need to create corresponding extension objects for BuilderStrategy. To do this, below we create a type ReflectionContainerExtension that inherits from UnityContainerExtension :

 1: public class ReflectionContainerExtension : UnityContainerExtension
 2: { 
          
 3: protected override void Initialize()
 4: { 
          
 5: this.Context.Strategies.AddNew<ReflectionBuilderStrategy>(UnityBuildStage.PreCreation);
 6: }
 7: }

In ReflectionContainerExtension, I just override the Initialize method. In this method, the BuilderStrategy pipeline of the corresponding UnityContainer is obtained through the Context property, and the AddNew method is called to add the ReflectionBuilderStrategy we created. The generic method AddNew accepts an enumeration of type UnityBuildStage. UnityBuildStage represents a certain stage of the entire Build process, where the position of the added BuilderStrategy in the pipeline is determined. Now we assume that the following object of type Foo needs to be created through UnityContainer:

 1: public class Foo
 2: { 
          
 3: public Guid Id { get; private set; }
 4:  
 5: public Foo()
 6: { 
          
 7: Id = Guid.NewGuid();
 8: }
 9: }

The actual provision of objects through UnityContainer is implemented in the following code. Since UnityContainer will pass UnityDefaultStrategiesExtension such an extension during the initialization process , I deliberately clear it by calling RemoveAllExtension. Then call AddExtension to add the ReflectionContainerExtension we created above to the extension list of UnityContainer. The last three calls to the Resolve method of UnityContainer get the Foo object and output the ID.

 1: static void Main(string[] args)
 2: { 
          
 3: IUnityContainer container = new UnityContainer();
 4: container.RemoveAllExtensions();
 5: container.AddExtension(new ReflectionContainerExtension());
 6: Console.WriteLine(container.Resolve<Foo>().Id);
 7: Console.WriteLine(container.Resolve<Foo>().Id);
 8: Console.WriteLine(container.Resolve<Foo>().Id); 
 9: }
 10: }

Here is the output:

b38aa0b4-cc69-4d16-9f8c-8ea7baf1d853
ef0cefc2-fax-4488-ad96-907fb568360b
08c538df-e208-4ef9-abe0-df7841d7ab60

4. Realize singleton mode by customizing BuilderStrategy

The above example can basically reflect the object provision mechanism of UnityContainer by means of BuilderStrategy pipeline. In order to further explain the existence of the "pipeline", we customize another simple BuilderStrategy to implement the singleton mode we are familiar with (based on the UnityContainer object, it is a singleton). The following is the BuilderStrategy that implements the singleton pattern: SingletonBuilderStrategy, and the corresponding Unity extension. In SingletonBuilderStrategy, we use a static dictionary to cache successfully created objects, and the key of the object in the dictionary is the type of the created object. The created object is cached in the PostCreate method and returned in PreCreate. In order to place the SingletonBuilderStrategy at the front end of the pipeline, specify UnityBuildStage as Setup when adding it.

 1: public class SingletonBuilderStrategy : BuilderStrategy
 2: { 
          
 3: private static IDictionary<Type, object> cachedObjects = new Dictionary<Type, object>();
 4:  
 5: public override void PreBuildUp(IBuilderContext context)
 6: { 
          
 7: if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type))
 8: { 
          
 9: context.Existing = cachedObjects[context.BuildKey.Type];
 10: context.BuildComplete = true;
 11: }
 12: }
 13:  
 14: public override void PostBuildUp(IBuilderContext context)
 15: { 
          
 16: if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type) || null == context.Existing)
 17: { 
          
 18: return;
 19: }
 20:  
 21: cachedObjects[context.OriginalBuildKey.Type] = context.Existing;
 22: }
 23: }
 24:  
 25: public class SingletonContainerExtension : UnityContainerExtension
 26: { 
          
 27: protected override void Initialize()
 28: { 
          
 29: this.Context.Strategies.AddNew<SingletonBuilderStrategy>(UnityBuildStage.Setup);
 30: }
 31: }

Now, we add the SingletonBuilderStrategy based extension to the previous program. Run our program again, and you will find that the output IDs are the same, so the objects created three times are the same.

 1: class Program
 2: { 
          
 3: static void Main(string[] args)
 4: { 
          
 5: IUnityContainer container = new UnityContainer();
 6: container.RemoveAllExtensions();
 7: container.AddExtension(new ReflectionContainerExtension());
 8: container.AddExtension(new SingletonContainerExtension());
 9: Console.WriteLine(container.Resolve<Foo>().Id);
 10: Console.WriteLine(container.Resolve<Foo>().Id);
 11: Console.WriteLine(container.Resolve<Foo>().Id); 
 12: }
 13: }

Output result:

254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42

Summary of how Unity IoC Container creates objects: Although the specific implementation mechanism of Unity is relatively complicated, its essence is the mechanism of Pipeline+Context based on BuilderStrategy+BuilderContext introduced in this article. When you are studying the specific implementation principles of Unity, grasping this principle will keep you from getting lost.

 

Guess you like

Origin blog.csdn.net/yetaodiao/article/details/131554677