Component Module
Defines a way to add Java extensions to the XWiki platform |
Type | JAR |
Category | |
Developed by | |
Rating | |
License | GNU Lesser General Public License 2.1 |
Bundled With | XWiki Enterprise, XWiki Enterprise Manager |
Table of contents
Description
XWiki's Architecture is based on Component-oriented Development (see Why use Components? to understand the benefits of using Components - Even though we're not using Plexus the advantages listed are the same).
There are several Component Manager solutions out there for Java. To name a few:
So far, XWiki has chosen to be independent of all existing Components Managers and instead to define some simple Component interfaces that can then be bound on any existing Component Manager. XWiki is currently implementing its own lightweight Component Manager. The future will depend on whether there'll be a standard solution in JavaSE (CDI is the best candidate so far). When a standard solution exists, we'll probably move to it.
Features
The Component Module defines the following features:
- Annotations to declare Component interfaces, Component implementations and Component dependencies (a.k.a as Component Requirements). .
- Ability to have Singleton Components and Per-lookup Components (a new instance is created when the component is retrieved).
- Ability to define a Hint to separate different Components implementations implementing the same Component interface.
- Automatic Field-based injection of Component Dependencies. Support for List and Map injections.
- Ability for Components to perform some initialization when they are instantiated.
- Ability for Components to log things.
- Component Events to be notified when a new Component is registered/unregistered in the system.
- Ability to define Component Realms, i.e. the ability to isolate groups of components.
- JSR330 Providers as Components and to have them injected automatically. Ability to define
Component Registration
There are two ways to register a Component:
- By setting Java Annotations on the Component Interface and the Component implementation and declaring the Component implementation in a META-INF/components.txt file.
- By programmatically registering the Component against the Component Manager instance.
Using Annotations
The following Annotations are available:
- ComponentRole: Used to declare an Interface as a Component Interface (a.k.a Role)
- Component: Used to declare a class implementing a Component Interface as a Component implementation
- InstantiationStrategy: Used to declare a Component implementation as being a singleton or not. See also the list of instantiation strategies
- Requirement: Used to declare a field as requiring a Component implementation to be injected at runtime.
- Inject: Used to declare a field as requiring a Component implementation to be injected at runtime
- Named: Used to give a Hint to the Role to specify which implementation should be injected.
Here's a quick example:
public interface Macro
{
List<Block> execute();
}
@Named("message")
@Singleton
public class MessageMacro implements Macro
{
@Inject
private Execution execution;
@Inject
@Named("box")
private Macro boxMacro;
public List<Block> execute()
{
...
}
}
In this example:
- The Macro interface is the Component Interface
- The MessageMacro class is declared as a Macro with a message Hint (to differentiate it from other implementations). Note that you can leave the hint empty, in which case it'll be the default hint.
- The MessageMacro needs 2 other components injected: Execution and Macro. The implementation injected will be found at runtime by the Component Manager. An Execution implementation with a default Hint will be injected and a Macro implementation with the box Hint will be injected.
In addition, for our MessageMacro component to be available at runtime, you need to list it with its fully-qualified name in a META-INF/components.txt file:
Registering and Using a Provider
Providers are useful for the following use cases:
- You wish to break a cyclic dependency
- You wish to lazily get a component instance (instead of it getting injected when your Component is looked up the first time)
- You wish to control how you return the instance you're providing for. For example you may wish to read the Component Hint from a configuration file and return the instance of the Component matching that Hint dynamically
Providers are registered as standard components with the @Component , @Named and @Singleton annotations. However unlike other Components you don't need to tag any interface with @ComponentRole. All you need to do is implement javax.inject.Provider as shown in this example:
@Singleton
public class ConfiguredQueryExecutorProvider implements Provider<QueryExecutor>
{
...
@Override
public QueryExecutor get()
{
... return instance here ...
}
}
In this example the ConfiguredQueryExecutorProvider Provider is in charge of returning instances of QueryExecutor.
To use it you would get it injected as follows:
@Inject
private Provider<QueryExecutor> queryExecutorProvider;
...
Note that you don't need to register a custom Provider to be able to get it injected. If no registered Provider is found the Component Manager injects a default Provider which simply does a lookup of the Component Role it provides using the Component Manager. This is very useful for "dynamic injection", i.e. the ability to lookup Components when you need them and not have them injected when your Component is first initialized. This allows your code to easily react to the registration of new Components at runtime.
Registering a Component with several Hints
You can register a component several times, for different hints. In this case several instances of the component will be registered. For example, to register our MessageMacro for the 3 hints info, error, warning you would use:
@Singleton
public class MessageMacro implements Macro
...
List and Map injections
You may want to have all the Component implementations of a given Component Interface injected. To do this you simply need to have a field of type List or Map defined.
For example to have all Macro implementations injected you'd use:
private List<Macro> macros;
or
private Map<String, Macro> macros;
In the second example the Map keys are the Hint values.
Getting access to the Component Manager
Automatic dependency injection is great and easy, but there are times when you don't know at compile time what you want injected. For these situations you can inject the ComponentManager. For example:
private ComponentManager componentManager;
See below for the API available on ComponentManager.
Overrides
Sometimes you'll have several JARs with Component implementations for the Component Interface and same Hint. In this case you need to tell the Component Manager which implementation to use. This is done by using a priority in your META-INF/components.txt files. The implementation with the highest priority will be used (highest means with the smaller value, 0 being the maximum priority and default priority is 1000 when not specified).
For example to register the com.acme.MyComponentImplementation component implementation with a priority of 500 you'd write:
Component Manager
The Component Manager is a key class when using Components. It allows you to lookup components and register new components programatically. Here's the API it offers:
<T> boolean hasComponent(Class<T> role, String roleHint);
<T> T lookup(Class<T> role) throws ComponentLookupException;
<T> T lookup(Class<T> role, String roleHint) throws ComponentLookupException;
<T> void release(T component) throws ComponentLifecycleException;
<T> Map<String, T> lookupMap(Class<T> role) throws ComponentLookupException;
<T> List<T> lookupList(Class<T> role) throws ComponentLookupException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor) throws ComponentRepositoryException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor, T componentInstance) throws ComponentRepositoryException;
void unregisterComponent(Class< ? > role, String roleHint);
<T> ComponentDescriptor<T> getComponentDescriptor(Class<T> role, String roleHint);
<T> List<ComponentDescriptor<T>> getComponentDescriptorList(Class<T> role);
There's only one instance of the root Component Manager (default hint) in the system.
Note that when you're writing a Component you normally don't even need to have access to it since all you need to do is declare dependencies using Annotations as explained above.
Component Initialization
If your Component implementation needs to perform some initialization, you'll need to make it implement the org.xwiki.component.phase.Initializable interface. For example:
@Singleton
public class DefaultObservationManager implements ObservationManager, Initializable
{
...
public void initialize() throws InitializationException
{
// Perform some init here.
}
}
You are then guaranteed that when your component is instantiated its initialize() method will be called.
Component Logging
If your Component implementation needed to log something it can get injected a SLF4J Logger automatically. For example:
...
@Component
@Singleton
public class DefaultObservationManager implements ObservationManager
{
@Inject
private Logger logger;
public void doSomething()
{
this.logger.info("Some info level logging");
...
}
}
Component Manager Initialization
If you're running your code inside an XWiki Enterprise environment (for example) then you won't need to bother about initializing the Component Manager since this is done automatically by the Container Module.
However if you're using XWiki as a library in your own application you'll need to initialize the Component Manager to be able to perform look ups of other Components.
This is easily achieved by using this:
EmbeddableComponentManager componentManager = new EmbeddableComponentManager();
componentManager.initialize(this.getClass().getClassLoader());
Tutorial
See the "Writing a XWiki Component" tutorial.
Internals
The default ComponentManager implementation is in EmbeddableComponentManager (in the xwiki-commons-component-default module). It's instantiated in the XWikiServletContextListener class (in the xwiki-platform-container-servlet module).