One of the expected features of a component container is the ability to manage the lifecycle of managed components. By lifecycle we mean the stages through which an object progresses; in plain-old Java code this entails its construction, its preparation and use, and its eventual release for garbage collection.
Seedling defines a more abstract lifecycle model with many extension points that allow you to write components that exhibit desired behavior at each stage of their lives.
The Seedling lifecycle model consists of three broad stages:
First, a node is provisioned, during which it's constructed, configured, and generally massaged into readiness.
Then, the node is installed, which attaches it into the Seedling hierarchy at a specific location, after first activating it as necessary. At this point the node is available for use by other parts of the application.
Finally, after leading a (hopefully) productive life, the node is uninstalled by being removed from the hierarchy and deactivated.
There's only one way to construct an object in Java: the
new
operator. However, newly-constructed objects are not
always immediately ready for use.
Seedling defines provisioning as the broader process
through which an
object is prepared for installation into the component hierarchy.
A node is provisioned indirectly and lazily when it's requested via
BranchNode.getChild()
or similar access methods: if the
requested node isn't already installed in the tree, Seedling will attempt
to provision and install it.
There are two provisioning mechanisms: aliasing and creation.
Aliasing is a near-trivial provisioning approach that simply identifies another installed node and inserts a reference to it at a separate location. This serves similar purposes to symbolic links in a file system: it allows the same object to appear in the hierarchy in several different contexts. Since aliasing is so simple, there's not much to say about it here; see the chapter on configuration to learn how to configure an alias.
Creation is when a new Java object is instantiated and configured. This process involves a few more steps and is used in the vast majority of cases. In fact, the ability to flexibly create nodes is one of the main reasons to use Seedling!
Creation of a node consists of a few distinct steps: construction, injection, and decoration.
Construction is the initial acquisition of the
object. The standard configuration language implements this step via the
.this
meta-property. Most nodes are constructed by
instantiation (keyword new
), but Seedling can also use
a factory method to construct objects indirectly.
Injection is the further modification of the object by calling property setter-methods, as declared by the node configuration.
Decoration takes the injected object and performs one last arbitrary transformation, perhaps resulting in a different object than we first constructed! This step can be used to implement the decorator pattern, to wrap the injected object with a dynamic proxy, or to do to other manipulations of the node before installation.
Installation of a node into the tree is a separate lifecyle phase from
provisioning. Seedling allows you to install nodes directly via
BranchNode.installChild()
, bypassing provisioning entirely.
Still, in most cases nodes are provisioned and installed automatically by
the framework.
Regardless of the source of the node, installation consists of two steps: activation and validation.
Activation signals the node that it's being installed into the component hierarchy, so it should start doing whatever it's supposed to do. The standard branches activate nodes using two callbacks.
If the node implements LocatableNode
, its
nodeInstalled
method is called with a
NodeLocation
indicating where the node is
being installed. This gives the node a reference to its parent
BranchNode
, from which it can access other nodes
in the container.
If the node implements ServiceNode
, its
startService
method is called.
Note | |
---|---|
Activation is not performed on aliases, since the target of the alias must already be active. |
After activation, the framework performs final validation of the node. This ensures that the node meets all declared constraints before it is exposed to the rest of the application. Typical constraints declare interfaces that must be implemented, but validation is a general process and Seedling will provide further hooks to check that a new node is worthy of installation.
If activation and validation succeed, the node is ready for business and the framework inserts it at the appropriate location in the tree so that other parts of the application can request it.
Sooner or later every node will need to be destroyed. This can
happen manually via BranchNode.uninstallChild()
, but usually
it happens transitively by deactivating a branch (or the entire Seedling
tree).
Compared to provisioning and installation, uninstallation is straightforward: the node is removed from the tree and then deactivated.
Deactivation signals the node that it's being removed from the component hierarchy, so it should stop doing its thing. The standard branches deactivate nodes much like it activates them.
If the node implements ServiceNode
, its
stopService
method is called.
And that's it, the node is out of the tree and can be released by the garbage collector (assuming other parts of the system haven't kept references to it).