DEV Community

Cover image for Creating A Maven Plugin
Isurumax26
Isurumax26

Posted on • Edited on

Creating A Maven Plugin

Introduction

Here, we'll make a Maven plugin to run a method of a different project from the plugin. Instead of POJO, this custom plugin is known as MOJO (Mavan Old Java Object).
I'll walk you through the steps involved in making this plugin in this blog.

Creating a Plugin

In this tutorial, we'll build the make-sound plugin, which accepts an object from a consumer project, initializes it there, executes the makeSound method in the consumer, and publishes the results to the console.

  • We create the plugin
  • Install it in the maven repo(.m2)
  • Consumer projects will consume it

Image description

To do this, first create a plugin-util module in your project. A few dependencies should be added to the project, and your pom file should appear as follows:

<dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.6.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-core</artifactId>
            <version>3.9.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.7.0</version>
            </plugin>
        </plugins>
    </build>

Enter fullscreen mode Exit fullscreen mode

Make sure dependencies are compatible with your java version.

 <groupId>org.example</groupId>
 <artifactId>plugin-util</artifactId>
 <version>1.0-SNAPSHOT</version>
 <packaging>maven-plugin</packaging>
Enter fullscreen mode Exit fullscreen mode

maven-plugin-api – necessary classes and interfaces to create our mojo
maven-plugin-annotation – handy to use annotations in our class
maven-project – lets us access the information about the project

packaging tag also very important because if not by default maven will package this as jar module

Then we need to create our Mojo class

@Mojo(name = "make-sound", defaultPhase = LifecyclePhase.COMPILE)

Enter fullscreen mode Exit fullscreen mode

Our custom class should extends the AbstractMojo class.

@Mojo – This provides us with our mojo's execution name and phase information. I've listed the mojo for the compilation process here. There are other steps, such as testing and deployment, among others.

Then we need to override the execute() method of the super class AbstractMojo

During the execution plugin will execute this method.

@Override
    public void execute() throws MojoExecutionException, MojoFailureException {

        try {
            Class<?> requiredClass = getClassLoader().loadClass(className);

            Animal animal = null;

            for (Constructor<?> constructor : requiredClass.getDeclaredConstructors()) {
                if (constructor.getParameterCount() == 0) {
                    animal = (Animal) constructor.newInstance();
                }
                else if (constructor.getParameterCount() == 1) {
                    animal = (Animal) constructor.newInstance();
                }
                else {
                    throw new IllegalAccessException();
                }

            }
            animal.makeSound();
        } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
            throw new RuntimeException(e);
        }


    }
Enter fullscreen mode Exit fullscreen mode

The plugin will only have access to its class loader during execution, which is crucial to keep in mind. It is unable to reach the consumer's class loader. At compile time, a ClassNotFoundException will be thrown if a consumer class is referenced in the plugin.

Therefore, during execution, you must add the class loader of the consumer to the class loader of the plugin. Using project objects, Maven offers a method for accomplishing this. We must include a MavenProject as a parameter in order to access the project data.
The approach I developed to include the consumer's class loader in the plugin's class loader is shown below.

 public ClassLoader getClassLoader() {
        try {
            Set<URL> urls = new HashSet<>();
            List<String> elements = project.getTestClasspathElements();
            //getRuntimeClasspathElements()
            //getCompileClasspathElements()
            //getSystemClasspathElements()
            for (String element : elements) {
                urls.add(new File(element).toURI().toURL());
            }

            ClassLoader contextClassLoader = URLClassLoader.newInstance(
                    urls.toArray(new URL[0]),
                    Thread.currentThread().getContextClassLoader());

            Thread.currentThread().setContextClassLoader(contextClassLoader);
            return Thread.currentThread().getContextClassLoader();


        } catch (DependencyResolutionRequiredException | MalformedURLException e) {
            return this.getClass().getClassLoader();
        }
    }

Enter fullscreen mode Exit fullscreen mode

You can now access the consumer classes. The consumer object in the plugin class must then be initialized, based on your requirements. The code snippet I used to create the consumer object in the plugin is provided below.

We normally overload constructors in java classes. In those circumstances, we must notify the plugin of the constructor to which we must refer.

try {
            Class<?> requiredClass = getClassLoader().loadClass(className);

            Animal animal = null;

            for (Constructor<?> constructor : requiredClass.getDeclaredConstructors()) {
                if (constructor.getParameterCount() == 0) {
                    animal = (Animal) constructor.newInstance();
                }
                else if (constructor.getParameterCount() == 1) {
                    animal = (Animal) constructor.newInstance();
                }
                else {
                    throw new IllegalAccessException();
                }

            }
Enter fullscreen mode Exit fullscreen mode

We now need to install the plugin module in the Maven repository after developing it. As a result, other modules will be able to use it as well.

So how we do it?

Just run the maven goals form clean -------> install then jar of the plugin will be installed to the maven repo

Adding the Plugin to the consumer module

This procedure is simple and straightforward. The name of your plugin goal, which you specify in the @Mojo annotation, is all that has to be done to add the plugin to the maven repo goal. A plugin may have multiple goals. We must define the plugin-specific parameters inside the configuration tag. I've included the class path for the class that I need to initialize here using the plugin's class Loader.

Keep in mind that we added the consumer's classLoader to the plugin's classLoader.

 <build>
        <plugins>
            <plugin>
                <groupId>org.example</groupId>
                <artifactId>plugin-util</artifactId>
                <version>1.0-SNAPSHOT</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>make-sound</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <className>org.example.Cat</className>
                </configuration>
            </plugin>
        </plugins>
    </build>
Enter fullscreen mode Exit fullscreen mode

Now you have created the plugin module and added it to the consumer class. You just need to build the consumer.
Run the consumer module from clean -------> test phase. Now check console output if you output anything you can see those in the console

Image description

The plugin has output "meow" as you can see. This came from the Cat class's makeSound method.

  public void makeSound() {
        System.out.println("meow");
    }
Enter fullscreen mode Exit fullscreen mode

Congratulations you have built a nice maven plugin !

Top comments (2)

Collapse
 
cicirello profile image
Vincent A. Cicirello

Hi. Nice post. I have a suggestion related to formatting. You can use syntax highlighting in your code blocks by specifying the language on the line with the backticks. Instead of starting the block with just 3 backticks, put Java on that same line as the backticks without a space. Likewise you can do the same with the XML code blocks by putting XML after the backticks.

Collapse
 
isurumax26 profile image
Isurumax26

Thanks Vincent for the suggestions. Will do it.