Page tree
Skip to end of metadata
Go to start of metadata

Your Rating: Results: 1 Star2 Star3 Star4 Star5 Star 110 rates
(warning) This article refers to components as in java objects that make up the foundation of the system and is not to be confused with "page components" in templating.

Abstract

Terms

  • Container, responsible for managing components.
  • Component, anything that's configured in and managed by a container.
  • ComponentProvider, is our container abstraction.

Introduction

Components provide some service such as keeping a registry of templates and expose functionality through an interface or API to other components. A component typically depends on a number of other components and benefits from dependency injection as Magnolia will connect components to satisfy their dependencies. Components are either singletons, which means that they are around for the lifetime of the server, or they are tied to shorter durations, for instance for the duration of an HTTP request or a user session. This is known as scope.

ComponentProvider

At the core of this is a ComponentProvider. It is responsible for creating and connecting components using dependency injection. It keeps each component mapped to a unique type, a Java class or an interface.

ComponentProviders can be arranged hierarchically having these properties:

  • A component in a child can rely on a component in the parent.
  • A component of a certain type can be configured in both the parent and the child.
  • They do not share life cycle but a parent must not be closed if it has children that have not been closed.

The key method provided by ComponentProvider is getComponent() which is used to get the component for a specific type.

public interface ComponentProvider {
  T getComponentClass<T> type);
}

Global access to the ComponentProvider

You will rarely have a need to interact with the ComponentProvider yourself, should you need to do so it can itself be injected into your component.

For cases where you need to get hold of the ComponentProvider and you cannot inject it there is a static lookup mechanism in info.magnolia.objectfactory.Components.

Defining components

Components are defined in a module's descriptor. Modules can define components of the same type. A definition in a module takes precedence over definitions in modules that it depends on. This allows modules to override and customize components in other modules.

The module descriptor can specify components for different containers. This is done using the id tag. You will rarely use a container other than main. Here is how the XML is structured:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module SYSTEM "module.dtd">
<module>
  ...
  <components>
    <id>main</id>
    <configurer>
      ...
    </configurer>
    <component>
      ...
    </component>
    <type-mapping>
      ...
    </type-mapping>
  </components>
</module>

Here are some examples:

A typical component definition

<component>
  <type>info.magnolia.cms.filters.FilterManager</type>
  <implementation>info.magnolia.cms.filters.FilterManagerImpl</implementation>
  <scope>singleton</scope>
  <lazy>true</lazy>
</component>

This defines a component of type FilterManager which is an interface. The actual implementation is FilterManagerImpl.

Scopes

By specifying the scope to be singleton we restrict this component to be created only once and then used for the whole lifetime of the application. Other available scopes are request and session.

Specifying scope is optional. If none is specified, the component is said to be non-scoped and will be created each time it is asked for or depended on. Every other component that depends on a non-scoped component will get an instance of its own.

The lazy flag is optional, defaults to true and only applies to singletons. When set to {false}}, the component will be created when the container it belongs to is started.

It is also possible to set the scope using an annotation on the implementing class. This is used when there is no scope set in the definition. In the other words, the XML overrides annotations. These annotations are available:

  • javax.inject.Singleton
  • info.magnolia.objectfactory.annotation.LazySingleton
  • info.magnolia.objectfactory.annotation.RequestScoped
  • info.magnolia.objectfactory.annotation.SessionScoped

Component via provider

<component>
  <type>info.magnolia.cms.beans.config.ServerConfiguration</type>
  <provider>info.magnolia.cms.beans.config.ServerConfiguration$InstanceFactory</provider>
  <scope>singleton</scope>
</component>

This defines a component of type ServerConfiguration. The actual implementation will be provided by ServerConfiguration$InstanceFactory. The provider acts as a factory and can take decisions on what to return. Supported types of provides are:

  • javax.inject.Provider
  • info.magnolia.objectfactory.ComponentFactory

The providers can have dependencies of their own.

Scope annotations are not available on providers.

Components from JCR

<component>
  <type>info.magnolia.cms.beans.config.URI2RepositoryManager</type>
  <workspace>config</workspace>
  <path>/server/URI2RepositoryMapping</path>
  <observed>true</observed>
  <scope>singleton</scope>
</component>

This defines a component of type URI2RepositoryManager that will be created using Content2Bean from a JCR repository. The workspace to use inside the repository is optional and defaults to config when omitted. The path however is mandatory. A component read from the repository can be observed, this means that after the component is read from JCR it will be recreated if the node it came from is changed. The process of recreating it is transparent to other components that use it. An observed component is always lazily initialized.

Registering components using code

Within the module descriptor you can define a ComponentConfigurer. The configurer will be called after definitions have been read from module descriptors and can add additional components. Configurers are called in order of module dependency.

ComponentProvider is also an Object Factory

ComponentProvider is also an object factory where you give it a type, an interface or a class and it will look up the implementation to use and instantiate an object of this. It is used by Content2Bean and in many more places. The ComponentProvider will provide dependency injection when instantiating the implementation.

The lookup is done using a type mapping.

Defining type mappings

A type mapping is defined in the module descriptor alongside components:

<type-mapping>
  <type>info.magnolia.cms.core.AggregationState</type>
  <implementation>info.magnolia.module.templatingkit.ExtendedAggregationState</implementation>
</type-mapping>

The ComponentProvider has a method for creating instances using these mappings:

public interface ComponentProvider {
  T newInstance(Class<T> type);
}

Additional constructor arguments

ComponentProvider can also take a set of additional arguments that will be used as constructor arguments in addition to those resolved using dependency injection.

public interface ComponentProvider {
  T newInstance(Class<T> type, Object... parameters);
}

It is also possible to pass in a list of custom parameter resolvers that will then assist in resolving the parameters to use.

public interface ComponentProvider {
  T newInstance(Class<T> type, ParameterResolver... parameters);
}

Legacy

In Magnolia versions prior to 4.5, components were configured as properties in the module descriptor. This mechanism still works for backwards compatibility reasons. Components configured this way were always lazy and served as both component definitions and type mappings.

This is an example of what it used to look like:

<properties>
  <property>
    <name>info.magnolia.module.templatingkit.sites.SiteManager</name>
    <value>info.magnolia.module.templatingkit.sites.STKSiteManager</value>
  </property>
  <property>
    <name>info.magnolia.cms.core.AggregationState</name>
    <value>info.magnolia.module.templatingkit.ExtendedAggregationState</value>
  </property>
</properties>

The equivalent component definition looks like this:

<components>
  <id>main</id>
  <component>
    <type>info.magnolia.module.templatingkit.sites.SiteManager</type>
    <implementation>info.magnolia.module.templatingkit.sites.STKSiteManager</implementation>
    <scope>singleton</scope>
    <lazy>true</lazy>
  </component>
  <type-mapping>
    <type>info.magnolia.cms.core.AggregationState</type>
    <implementation>info.magnolia.module.templatingkit.ExtendedAggregationState</implementation>
  </type-mapping>
</components>

In order to remain fully backwards compatible, Magnolia 4.5 will add both a component definition and a type mapping for AggregationState.

Supported types of dependency injection

Magnolia uses the standardized annotations for dependency injections from JSR-330 which enable all three types of dependency injection:

  • Constructor injection
  • Field injection
  • Setter injection

This is an example of all three:

public class RenderingEngine {

    // Dependencies are injected in the constructor
    @Inject
    public SomeComponent(ModuleManager moduleManager) { ... }

    // Fields are set with injected dependencies after construction
    @Inject
    private TemplateRegistry templateRegistry;

    // Setters are called with injected dependencies after construction
    @Inject
    public void setRendererRegistry(RendererRegistry rendererRegistry) { ... }
}

Note that the constructor has been annotated with @Inject. This is necessary if it needs dependencies. If no constructor is annotated with @Inject the default constructor taking no parameters will be used.

It is also possible to inject properties like this:

  @Inject
  @Named("magnolia.develop")
  private Provider<String> developmentMode;

Javadoc for JSR-330

Tips and advice

Try to avoid field injection since it makes the component harder to test.

When accessing an object in a lesser scope, inject a Provider and call it when you need the object. For instance AggregationStateBasedRenderingContext is scoped with @RequestScoped. To access it the rendering engine depends on it like this:

public class RenderingEngine {
    public RenderingEngine(Provider<RenderingContext> renderingContextProvider) { ... }
}

Component life cycle

A component has a life cycle with these steps

  • Instantiation, the constructor annotated with @Inject is called with arguments resolved by the container or the default no-args constructor is used.
  • Fields annotated with @Inject are filled.
  • Methods annotated with @Inject are called with arguments resolved by the container.
  • Component is in use.

Google Guice integration

Magnolia uses Google Guice to provide dependency injection. It is possible to access the Guice binder and do bindings directly on it. You do this by adding a ComponentConfigurer that inherits from info.magnolia.objectfactory.guice.AbstractGuiceComponentConfigurer.

Magnolia Startup and Shutdown sequence

It is important to know when a container is started so you know what will be available to your components and what assumptions you can make about the state of the system. Or more specifically:

When the main container starts up and creates all its eager singletons the repository can be empty as it has not yet run version handlers.

Startup

When Magnolia starts up it goes through a number of steps that start up services. In the example above the manager relies on properties having been loaded earlier and are available to it when it is created. Another assumption that typical components will make is that the content repositories are started.

To achieve this we have three main steps, or stages, that the application goes through at startup.

Step 1 Platform Container

In this step we focus on loading properties. Since properties can come from modules the ModuleManager is started in this step.

Step 2 System Container

In this step we start up fundamental system services that components starting in the next step rely on. This includes starting up content repositories.

Step 3 Main Container

Then we start the application for real. All eager singletons will be created first.

Step 4 Update/Install -mode

Then version handlers are run if we have started in update/install mode.

Step 5 Start modules

And finally we start up the modules by invoking ModuleLifecycle.start().

Shutdown

When Magnolia is shutdown it performs the same steps in reverse. First calling ModuleLifecycle.stop() on modules, then it closes Main, then System and finally Platform.

Magnolia ComponentProvider hierarchy

  • No labels

11 Comments

  1. I was playing around with 4.5 and IoC, and the following came to mind

    • <components><id>:
      • this is the container ID, right ? Perhaps we could rename the tag accordingly, to make it more obvious ?
      • it seems the value used there is not validated - at first I had no idea what this was for, so I added a random value, and I don't think any error was reported.
      • if "main" is what's going to be used in 99% of the cases, perhaps make it non-mandatory ?
    • can we define a component only by its impl ? (and still declare dependencies on the interface)
    • can we get List<MyCompo> injected ?
    • are <scope> and <lazy> mandatory ? can they be @annotated ? if both, do we validate/reject ?
      • <components><id> is the container id, +1 for renaming it and making it default to 'main'
        • its not validated as we don't know up front which containers will be around, i.e. modules can have their own and in 5.0 we plan to use child containers for the ui
      • we cannot declare an impl and then refer to it using an interface it implements or one of its super classes
        • since Guice binds a type to an implementation we need to use the type that was registered
      • we cannot inject List<SomeInterface> to get all components that implement that interface
        • we can only inject based on the type that serves as the key to an instance, only one instance can be bound to a type
      • scope and lazy are optional, see the section on Scopes, they can be annotated, we do validate and reject unknown scopes
        • if we rename and make optional, validation becomes less important, imo. otoh, perhaps we could validate "after the fact" - i.e once all containers have been created/started, see if anything is left unregistered ?
        • What if I have a component that implements two interfaces - and I'd like the same instance to be injected in components that depend on InterfA as well as those that depend on InterfB ?
        • List<SomeInterface> - perhaps via some sort of provider then ?
        • missed the "the XML overrides annotations" bit, thanks.
          • not really as they have varying life span, a container might be alive only to serve one user (like the ui for that user) and even only for a request
          • there's currently no way to represent that in xml
          • that can work, you might have to base it only on the type they're registered by though
  2. so it looks like we didn't rename <components><id> after all ?

    Another question - as unlikely as it might be (although you mention ui-specific containers in 5.0, so this might become a reality soon), how does it work if one needs to register several components in different containers ?

    (i'd be tempted to drop <id> altogether and replace it with an optional <containerId> in the <component> element ?)

    1. When you need to define components using more than one id you just add another <components> tag. See the module descriptor for ui-admincentral for an example fo this.

      In this file you'll also see the ids used for the 5.0 UI. There's components defined with id = app and id= subapp. These go into all apps and subapps. So the pages-app gets components from id=app and id=app-pages.

      As it stands the id is not a 1 to 1 reference to a component provider. Its rather a grouping key where we in the code grab all the components defined with a certain id, respecting the order of modules to allow overriding, and create a component provider using these. In the case of id=app and id=subapp we combine components using different ids.

      1. Ha, by looking at the dtd, I didn't realize you could declare multiple <components> elements, thanks.

  3. Hi Tobias,

    What about @LocalScoped, is the <scope>request</scope> now as well <scope>local</scope> ?

    Thanks,

    Karel.

     

    1. Yes, the name is now local

  4. Awesome page! Would be great to have this information in the official documentaiton.Could we also have more information how type mapping works? and what is a configurer?

    1. <configurer><class> is to register implementations of info.magnolia.objectfactory.configuration.ComponentConfigurer for low-level stuff.

      Type mapping is for *toBean, to specify default implementation of configured components when the configuration doesn't specify it through a class property.