itemis Blog

CDI-based dependency injection for Maven Plugins

Written by Stanley Hillner | Sep 12, 2016

Developing Apache Maven plugins is pretty straight forward and well described in the Guide to Developing Java Plugins. Besides some drawbacks the development of simple plugins is not a pain. But when it comes to more complex Mojos with a larger number of parameters and a more sophisticated business logic there is not much support for real modularization. Everything has to be implemented in a sole processing method called execute() and the developer is left alone with topics like outsourcing and orchestration of business logic parts or failure handling. For most of the plugins such support isn't even necessary but there are also a number of plugins that could profit from a richer processing model. Read on to learn more about how to elegantly solve these problems.

Fortunately there is now a library trying to close this gap by implementing some nice features supporting the creation of advanced plugins with higher complexity. This library is called Maven CDI Plugin Utils, is hosted on Github and can be found on Maven Central. The core features of this library have been implemented during the development of the Unleash Maven Plugin but it evolved fast to an independent project that provides the following features for plugin developers.

CDI-based dependency injection with classpath-based auto wiring

Dependency Injection (DI) is a common way to realize decoupling of complex systems. Why should the implementor of a feature care about the concrete relization of another feature he depends on? If a clear API exists it does not matter which implementation of the API is used at runtime as long as the consumers of that API can rely on this contract.

DI serves exactly this point. It does not matter where your data comes from as long as the contract is respected. Data can simply be injected at the point where it is needed instead of grinding it through the whole application to the one method that requires this data.

Maven CDI Plugin Utils implements the dependency injection using Weld SE which provides CDI-based dependency injection for Java SE applications. The following concepts are supported:

  • Dependency Injection using @javax.inject.Inject
  • Usage of qualifier annotations and CDI alternatives
  • providing class instances and data using CDI producer methods or fields
  • Usage of @javax.annotation.PostConstruct and @javax.annotation.PreDestroy for easy implementation of initialization and teardown logic
  • Usage of CDI events for inter bean communication without compile-time dependencies 

Some people might say that Maven already comes with a CDI integration providing dependency injection for Mojo implementations. Yes, the integration of JSR330 is described here but there are some major drawbacks:

  • The DI approach only works if you create a Sisu index containing an enumeration of all classes that are injectable. It is not possible to inject classes of libraries that do not contain such an index. The official reason for this is to keep the startup times fast by disabling classpath scanning.
  • There is only support for @Inject, @Named and @Singleton. The usage of producers, custom qualifiers or events is not possible.
  • This integration of the DI approach supports only dependency injection within the plugin but nothing else. 

And here is how the library addresses these problems:

  • There is no need for any indices at all, just add a beans.xml as a marker file to your META-INF directory and bean discovery is done by scanning the classpath of the plugin. Applying this method for bean discovery allows you to inject classes from external libraries and plugin dependencies as well. Sure, startup time is a bit slower than without classpath scanning but the scanned classpath consists only of the plugin classes and its dependencies. The project classpath is not considered which makes classpath scanning pretty fast.
  • DI is implemented using Weld SE and thus it is possible to do everything that is supported by Weld SE!
  • DI is only one part of the library. Other interesting concepts are the workflow-based processing model, the implicit rollback feature as well as the extensibility of each plugin that is implemented using this library. More details follow below.

Workflow-based processing model supporting modularization and reusability of business logic

Building on this library a plugin has to implement its business logic as so-called processing steps. The intention is that each step encapsules only a small and well defined part of the whole logic instead of implementing too much stuff at one point. This gives the developer the possibility to reuse existing steps in other contexts later. A detailed description of how to implement processing steps can be found in the Wiki of the library.

Since the implementation of some processing steps is just the first step but does not yet couple them to the Mojo itself, the CDI Plugin Utils library relies on a workflow-based processing model to orchestrate available processing steps and provide the orchestrated processing logic to the Mojo. This concept is described in the workflow format section of the project Wiki.

Workflows are mainly an ordered collection of processing step IDs that are available on the classpath of the plugin. This collection specifies the order in which the steps shall be executed and may also declare parallel executions. Each Mojo must provide a default workflow which is simply a text file named after the Mojo. All default workflows of a plugin are located under plugin.jar/META-INF/workflows and are automatically loaded by the bootstrapping of the AbstractCDIMojo.

Implicit rollback features

Besides the DI-based workflow processing model this library comes also with an integrated rollback concept allowing plugin Mojos to revert their processings in case of an error. All to be done is to implement a reverse operation at the points where this would be necessary. The orchestration of the reverse operations is derived from the processed workflow and the reverse workflow is executed implicitly in case of an error. If the rollback of the Mojo's steps is implemented correctly the project and all involved resources should be in the same state as before the execution of the goal if an error occurred.

Implementing the reverse processing logic is also done in the processing steps and is thus only necessary for this small, well defined part of the whole process that a specific step serves. There are numerous possibilities to register reverse operations for a processing step. Typically only steps have to provide rollback logic that produce side effects during their actual execution, such as the modification of external resources (filesystem, servers, databases) or the modification of project resources that are not covered by the clean lifecycle. All other steps can simply omit rollback implementations.

Extensibility by design

Another key feature of the library is that it turns Mojos into extensible software units. Users of the plugin are now able to extend or restrict the processing logic of the goal to execute by simply adding custom processing steps to the classpath of the plugin and overriding the default workflow of that goal.

Some additional general purpose processing steps that only rely on the configuration data provided by the Execution Context are available here: maven-cdi-plugin-hooks. This small library comprises f.i. the following steps:

  • A shell execution hook with id exec that is able to execute shell commands and scripts
  • A hook with id httpRequest for the execution of HTTP requests such as GET, POST, PUT or DELETE
  • A hook for executing Apache Maven goals (id mvn

Conclusion

The proposed library (Maven CDI Plugin Utils) enables the usage of CDI-based dependency injection for Apache Maven plugins and much more. It is now possible to build failure-tolerant and extensible Maven plugins with a stronger focus on reusability and a cleaner design with minimal effort. Although this support is not necessary for most of the plugins there are numerous cases where these concepts can massively gain quality, stability and reusability.

A sample plugin that benefits from all of the concepts of this library is the Unleash Maven Plugin which provides better release functionality for Maven than the maven-release-plugin does.