News and Notes from the Makers of Nexus | Sonatype Blog

Plexus Container Five Minute Tutorial

Written by Brian Demers | May 21, 2009

The goal of this blog is to show an updated and more involved example then what is currently located on the plexus site. This will cover creating a couple of components and explain the different ways to inject your dependencies. This example assumes you are using maven 2 to make your life easier.


Creating a Component Interface

The first task to creating a component is to define its role. In Java, this usually takes the form of defining an interface with the functionality the component will expose.

Note: Plexus does not strictly require you use an interface to define the role, however it is strongly recommended to help improve your application design.

package example;

public interface Cheese
{
    /**
    * Slices the cheese for apportioning onto crackers.
    * @param slices the number of slices
    */
    void slice( int slices );

    /**
    * Get the description of the aroma of the cheese.
    * @return the aroma
    */
    String getAroma();
}

That is it, nothing special, just an interface. We will use the class name as the role, in this example it will be Cheese.class

Creating a Component Implementation

Once an interface is declared, you need to create one or more implementations of the functionality declared by it.

package example;

import org.codehaus.plexus.component.annotations.Component;

/**
 * All we need to tell Plexus that this is a component.
 */
@Component( role = Cheese.class, hint = "parmesan" )
public class ParmesanCheese
    implements Cheese
{
    public void slice( int slices )
    {
        throw new UnsupportedOperationException( "No can do" );
    }

    public String getAroma()
    {
        return "strong";
    }
}

All you need to do is add the @Component annotation, to define the role (the interface name from above) and a hint (to identify a unique implementation).

Creating a Component Descriptor

Other then telling you how to create it, and what it is for, I am not going to tell you much about it. This file will be generated for you in META-INF/plexus/components.xml inside your jar.

The component descriptor describes the component and its dependencies (we will get to that next). All you need to do is configure the plexus-component-metadata plugin in your pom. And of course you need to add your maven dependencies.

<project>
...
  <dependencies>

    <!-- The Plexus annotations -->
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-component-annotations</artifactId>
      <version>1.0-beta-3.0.5</version>
    </dependency>

    <!-- The plexus container -->
    <dependency>
      <groupId>org.codehaus.plexus</groupId>
      <artifactId>plexus-container-default</artifactId>
      <version>1.0-beta-3.0.5</version>
    </dependency>
...
  </dependencies>
 <build>
...
<plugins>
...
      <!-- Needed to process the annotations to create an xml file -->
<plugin>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-component-metadata</artifactId>
        <version>1.0-beta-3.0.5</version>
        <executions>
          <execution>
            <id>process-classes</id>
            <goals>
              <goal>generate-metadata</goal>
            </goals>
          </execution>
          <execution>
            <id>process-test-classes</id>
            <goals>
              <goal>generate-test-metadata</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
...
<plugins>
</project>

Injecting Dependencies

The above is all great and stuff but so far we haven't injected anything yet.

First we will add another Cheese. This will be the default implementation.

package example;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;

/**
 * AmericanCheese is the default cheese in most places. So here we are using it as the default component. Note: no
 * <code>hint</code> is specified.
 */
@Component( role = Cheese.class )
public class AmericanCheese
    implements Cheese
{
    @Requirement
    Logger logger;

    public void slice( int slices )
    {
        this.logger.info( "Slicing the cheese: " + slices + " times." );
    }

    public String getAroma()
    {
        return "plastic";
    }
}

Then we will create another interface/component. Something simple:

package example;

public interface Plate
{
    void printItems();
}

And now for the fun!

Create a Plate component

package example;

public class CheesePlate
    implements Plate
{
...

  public void printItems()
  {
    ...
  }
}

To inject the default component, you just add the @Requirement to you field. Plexus assumes the role is the type of the field. In this case the field is a Cheese

  @Requirement
  private Cheese americanCheese;

This does the same as the above snippet, but is more verbose.

  @Requirement( hint = "default" )
  private Cheese moreAmericanCheese;

To inject a specific implementation use the component`s hint.

  @Requirement( hint = "parmesan" )
  private Cheese parmesanCheese;

To inject all the implementations of a component, just define the role.

  @Requirement( role = Cheese.class )
  private List<cheese> cheeses;

You can also inject the components as a Map, the key of the entry is the component`s hint.

  @Requirement( role = Cheese.class )
  private Map<string, Cheese> cheeseMap;

Put Everything Together

The following class demonstrates the different dependency techniques.

package example;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;

@Component( role = Plate.class )
public class CheesePlate
    implements Plate
{
    /**
     * Inject the default cheese.
     */
    @Requirement
    private Cheese americanCheese;

    /**
     * Inject the default cheese, this does the same as the previous line.
     */
    @Requirement( hint = "default" )
    private Cheese moreAmericanCheese;

    /**
     * Inject the ParmesanCheese.
     */
    @Requirement( hint = "parmesan" )
    private Cheese parmesanCheese;

    /**
     * Inject all the cheese.  The container automatically add all the components defined with the role <code>Cheese.class</code>.
     */
    @Requirement( role = Cheese.class )
    private List<cheese> cheeses;

    /**
     * You can also inject the components as a Map, where the key is the Components <code>hint</code>.
     */
    @Requirement( role = Cheese.class )
    private Map<string, Cheese> cheeseMap;

    public void printItems()
    {
        System.out.println( "americanCheese smells like: " + americanCheese.getAroma() );
        System.out.println( "moreAmericanCheese smells like: " + moreAmericanCheese.getAroma() );
        System.out.println( "parmesanCheese smells : " + parmesanCheese.getAroma() );

        System.out.println( "\nThe List of cheeses has:" );
        for ( Cheese cheese : this.cheeses )
        {
            System.out.println( "cheese: " + cheese.getClass().getSimpleName() );
        }

        System.out.println( "\nThe Map contains:" );
        for ( Entry<string, Cheese> entry : this.cheeseMap.entrySet() )
        {
            System.out.println( "hint: " + entry.getKey() + ", value: " + entry.getValue().getClass().getSimpleName() );
        }
    }
}

Executing the Plexus Application

The final step is to execute the application that uses this component. In this example, you will use an container from a standard Java class with a main() method.

Creating the container

Creating the container is very simple:

package example;

import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;

public class Example
{
    public static void main( String[] args )
        throws Exception
    {
        // create a new container
        PlexusContainer container = new DefaultPlexusContainer();

        ...

        // stop the components and container
        container.dispose();
    }
}

That's all there is to it: create the container, and start it. Defaults and the current classloader will be used.

Retrieving the Component

To retrieve the Plate component from the container and execute it's printItems() method, add the following lines after those that start the container:

  Plate plate = container.lookup( Plate.class );
  plate.printItems();

That's all there is to getting started with Plexus. Congratulations!