Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
Sisu/PlexusMigration
Contents
Overview
This document provides some details on how to convert legacy Plexus components into modern JSR-330 components.
Annotations
For brevity of examples imports are omitted. The following table defines the meanings and fully-qualified-class-names of the annotations references in the following examples.
Annotation | Class | Description |
---|---|---|
@Component |
org.codehaus.plexus.component.annotations.Component | Legacy Plexus component annotation |
@Requirement |
org.codehaus.plexus.component.annotations.Requirement | Legacy Plexus injection annotation |
@Configuration |
org.codehaus.plexus.component.annotations.Configuration | Legacy Plexus configuration annotation |
@Named |
javax.inject.Named | Standard JSR-330 annotation to provide component name |
@Singleton |
javax.inject.Singleton | Standard JSR-330 annotation to mark component as singleton |
@Typed |
javax.enterprise.inject.Typed | JavaEE annotation to mark component type |
@Description |
org.sonatype.inject.Description | Sisu-specific annotation to provide a description for a component |
@Parameters |
org.sonatype.inject.Parameters | Sisu-specific annotation to mark Map<String,String> injection as container context parameters. |
@Inject |
javax.inject.Inject | Standard JSR-330 annotation to mark field, parameter, method for injection |
@Nullable |
javax.annotation.Nullable | Standard JSR-305 annotation to mark field, parameter, result value as potentially returning null value |
References
http://www.jcp.org/en/jsr/detail?id=330
http://www.jcp.org/en/jsr/detail?id=305
@Component
Plexus @Component
annotations are replaced by standard @Named
, @Singleton
, etc annotations.
Singletons
For example this Plexus component:
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { }
can be converted to:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { }
Instance
By default in Plexus, components are singletons, but this is not the case for every component. This Plexus component is not a singleton:
- Plexus
@Component(role=Component.class, hint="my", instantiationStrategy="per-lookup") public class MyComponent implements Component { }
and is converted to:
- Converted
@Named("my") public class MyComponent implements Component { }
Notice this is the same as the example above, except with-out the @Singleton
annotation.
Type Override
By default the type of the component is determined automatically, though in some rare cases an explicit type is required. To specify the explicit binding type use the @Typed
annotation:
- Converted
@Named("my") @Typed(Component.class) public class MyComponent extends SomeSupportClassHardToGuessTypeFrom { }
Descriptions
In some cases component descriptions are required. There is no standard annotation to provide this, however Sisu provides a custom annotation for this.
- Plexus
@Component(role=Component.class, hint="my", description="My custom component") public class MyComponent implements Component { }
becomes:
- Converted
@Named("my") @Singleton @Description("My custom component") public class MyComponent implements Component { }
@Requirement
Basics
@Requirement
defines injection points for legacy Plexus components. These more-or-less line-up directly with replacement with @Inject
, though there are more options available as @Inject
is support for fields, constructors and methods, where @Requirement
only worked with fields. The recommended option is to replace legacy Plexus injection with constructor injection where possible.
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Requirement private AnotherComponent another; }
becomes:
- Converted using recommended constructor injection
@Named("my") @Singleton public class MyComponent implements Component { private final AnotherComponent another; @Inject public MyComponent(final AnotherComponent another) { this.another = another; } }
Use of constructor injection in this fashion has some impact on replacing legacy Plexus lifecycle Initializable
and Contextualizable
interfaces, which often only exist to perform setup once injection is performed.
Alternatives
Other options for conversions using field injection:
- Converted using field injection
@Named("my") @Singleton public class MyComponent implements Component { @Inject private AnotherComponent another; }
or method injection:
- Converted using method injection
@Named("my") @Singleton public class MyComponent implements Component { private AnotherComponent another; @Inject public setAnotherComponent(final AnotherComponent another) { this.another = another; } }
Optional
Optional components are configured to be @Nullable
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Requirement(optional=true) private AnotherComponent another; }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final AnotherComponent another; @Inject public MyComponent(final @Nullable AnotherComponent another) { this.another = another; } }
Names and Hints
Legacy Plexus component hints become @Named
:
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Requirement(hint="foo") private AnotherComponent another; }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final AnotherComponent another; @Inject public MyComponent(final @Named("foo") AnotherComponent another) { this.another = another; } }
Types
Legacy Plexus component roles, which are normally only used for collection types are generally not needed:
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Requirement(role=AnotherComponent.class) private List<AnotherComponent> components; }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final List<AnotherComponent> components; @Inject public MyComponent(final List<AnotherComponent> components) { this.components = components; } }
@Configuration
Plexus configuration injection is handled by @Inject
@Named("${expression}")
injection.
Basics
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Configuration(name="configDir") private File configDir; }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final File configDir; @Inject public MyComponent(final @Named("${configDir}") configDir) { this.configDir = configDir; } }
Defaults
Default values are provided by expression syntax.
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component { @Configuration(name="configDir", value="defaultDir") private File configDir; }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final File configDir; @Inject public MyComponent(final @Named("${configDir:-defaultDir}") configDir) { this.configDir = configDir; } }
Lifecycle Support
This section is specific to how to adapt legacy Plexus component lifecycle interfaces. There is no hard-fast way to adapt these, but there are some guidelines to follow.
Interface | Class | Description |
---|---|---|
Initializable |
org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable |
Hook was used to inform a component once its injection has been performed. |
Contextualizable |
org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable |
Similar to Initializable but passes in the container context. |
Startable |
org.codehaus.plexus.personality.plexus.lifecycle.phase.Startable | Allows components to be started and stopped. |
Disposable |
org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable | Hook used to inform a component that it is no longer available in the container. |
Initializable
By and far this can be replaced by using constructor-inject, and performing the initialize()
at the end of the constructor.
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component, Initializable { @Requirement private AnotherComponent another; public initialize() throws InitializationException { another.init(); } }
becomes:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { private final AnotherComponent another; @Inject public MyComponent(final AnotherComponent another) { this.another = another; another.init(); } }
Contextualizable
Similar to Initializable
, though if the context is needed you can inject the container context parameters with:
- Converted
@Named("my") @Singleton public class MyComponent implements Component { @Inject public MyComponent(final @Parameters Map<String,String> params) { // do something with _context_ params } }
Startable
Support for JSR250 lifecycle annotations is still being worked on (see bug 386446). Until that feature is ready applications can use other techniques to trigger start/stop behavior. The examples below show the solution used in Sonatype Nexus, which relies on a modified implementation of the Google-Guava EventBus to handle lifecycle events.
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component, Startable { public start() throws StartingException { // do something to "start" } public stop() throws StoppingException { // do something to "stop" } }
becomes:
- Converted
import org.sonatype.sisu.goodies.eventbus.EventBus; import com.google.common.eventbus.Subscribe; @Named("my") @Singleton public class MyComponent implements Component { private final EventBus eventBus; @Inject public MyComponent(final EventBus eventBus) { this.eventBus = eventBus; eventBus.register(this); } @Subscribe public on(final NexusStartedEvent event) throws Exception { // do something to "start" } @Subscribe public on(final NexusStoppedEvent event) throws Exception { // do something to "stop" eventBus.unregister(this); } }
Disposable
Similar to Startable
use of events are used to handle replacement for Disposable components.
- Plexus
@Component(role=Component.class, hint="my") public class MyComponent implements Component, Disposable { public dispose() { // do something to "dispose" } }
becomes:
- Converted
import org.sonatype.sisu.goodies.eventbus.EventBus; import com.google.common.eventbus.Subscribe; @Named("my") @Singleton public class MyComponent implements Component { private final EventBus eventBus; @Inject public MyComponent(final EventBus eventBus) { this.eventBus = eventBus; eventBus.register(this); } @Subscribe public on(final NexusStoppedEvent event) throws Exception { // do something to "dispose" eventBus.unregister(this); } }
Custom Bindings
Plugins which require additional custom bindings can provide a @Named
Guice module to configure components bindings further.
Sisu will automatically load modules which are @Named
and apply them to the injectors bindings. These modules are really no different than normal Guice modules, except that they need to have the @Named
annotation on them so that Sisu can locate them when initializing.
- Converted
@Named public class MyPluginModule extends com.google.inject.AbstractModule { public void configure() { bind(Component.class).to(MyComponent.class); } }