You may have noticed in this chapter's example how we keep referring to the code that contains our object definitions as a container, and yet, the class used to create it is called ApplicationContext
. This chapter will clarify the differences between a context and a container.
Spring Python has a base class called ObjectContainer
, which is responsible for managing the object definitions, creating instances of objects based on these definitions, and is largely responsible for the functionality that we have looked at so far. Any instance of this class would be a container in object oriented terms. In fact, we could substitute that class in our previous example everywhere we see ApplicationContext
, and it would act mostly the same.
ObjectContainer
has a subclass called ApplicationContext
. An instance of ApplicationContext
is a context. However, from an OOP perspective, a context is a container. So generally referring to instances of either one as an IoC container is common practice.
An ApplicationContext
has some differences in behavior from an ObjectContainer
as well as extra features involving life cycle management and object management. These differences will first be shown in the following table of features and will be covered in more detail later.
Class |
Features |
---|---|
|
|
|
|
Earlier I said that most of the behavior in this chapter's example would be the same if we had replaced ApplicationContext
with ObjectContainer
. One difference is that ObjectContainer
doesn't instantiate any objects when the container is first created. Instead, it waits until the object is requested to actually create it. ApplicationContext
automatically instantiates all objects immediately when the container is started.
It is possible to override this by setting lazy_init
to True
in the object's definition. This will delay object creation until first request.
class WikiTestAppConfig(WikiProductionAppConfig): def __init__(self): super(WikiTestAppConfig, self).__init__() @Object(lazy_init=True) def data_access(self): return StubDataAccess()
One reason for using this is if we had some object that was only needed on certain occasions and could incur a lot of overhead when created. Setting lazy_init
to True
would defer its creation, making it behave as an on-demand service.
This override works for both ObjectContainer
and ApplicationContext
.
Another key duty of the container is to also manage the scope of objects. This means at what time that objects are created, where the instances are stored, and how long before they are destroyed.
Spring Python currently supports two scopes: singleton and prototype. A singleton-scoped object is cached in the container until shutdown. A prototype-scoped object is never stored, thus requiring the object factory to create a new instance every time the object is requested from the container.
The default policy for the container is to make everything singleton. The scope for each object can be individually overridden as shown below.
class WikiTestAppConfig(WikiProductionAppConfig): def __init__(self): super(WikiTestAppConfig, self).__init__() @Object(scope=scope.PROTOTYPE) def data_access(self): return StubDataAccess()
Each request for data_access
will yield a different instance. This happens whether the request is from outside the container, or from another container object injecting data_access
.
This override works for both ObjectContainer
and ApplicationContext
.
ApplicationContext
will invoke after_properties_set()
on any object that has this method, after it has been created and all container-defined properties have been set. Here are some examples of how this can be useful:
PyroServiceExporter
launches a daemon thread in the background after all properties are set.
ApplicationContext
will search for post processors. These are classes that manipulate other objects in the container. They extend Spring Python's ObjectPostProcessor
class.
class ObjectPostProcessor(object): def post_process_before_initialization(self, obj, obj_name): return obj def post_process_after_initialization(self, obj, obj_name): return obj
When the context starts up, all non-post processors are fed one-at-a-time to each post processor twice: before the initialization and after. By default, the object is passed through with no change. By coding a custom post processor, we can override either stage, and apply any changes we wish. This can include altering the object itself, or substituting a different object.
class RemoteService(ObjectPostProcessor): def post_process_after_initialization(self, obj, obj_name): if obj_name.endswith("Service"): return PyroServiceExporter(service=obj, service_name=obj_name, service_port=9000) else: return obj
This post processor looks at an object's name, and if its name ends with Service
, returns a proxy that exports the object as a Pyro service, using the object's name as part of the URL. Otherwise, it simply returns the object.
This example shows how we can easily develop a convention-over-configuration client-server mechanism, simply by using Spring Python's IoC container and some custom post processors.
3.145.111.116