You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 16 Next »

(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 forms of dependency injection

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

  • Constructor injection
  • Setter injection
  • Field injection

This is an example of all three:

public class RenderingEngine {

    @Inject
    private TemplateRegistry templateRegistry;

    @Inject
    public SomeComponent(ModuleManager moduleManager) { ... }

    @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.

Components with methods annotated with @PreDestroy are called when the step in which they live is closed.

  • No labels