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
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>
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>
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)
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);
}
}
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();
}
}
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();
}
}
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>
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
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");
}
Congratulations you have built a nice maven plugin !
Top comments (2)
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.
Thanks Vincent for the suggestions. Will do it.