- Introduction
- Important Notice: Plugin Naming Convention and Apache Maven Trademark
- Your First Plugin
- Your First Mojo
- A Simple Mojo
- Project Definition
- Building a Plugin
- Executing Your First Mojo
- Shortening the Command Line
- Attaching the Mojo to the Build Lifecycle
- Mojo archetype
- Parameters
- Defining Parameters Within a Mojo
- Configuring Parameters in a Project
- Using Setters
- Resources
- Разработка java система плагинов
- gradle init —type java-application
- ./gradlew build
- ./gradlew run
Introduction
This guide is intended to assist users in developing Java plugins for Maven.
Important Notice: Plugin Naming Convention and Apache Maven Trademark
You will typically name your plugin -maven-plugin .
Calling it maven—plugin (note «Maven» is at the beginning of the plugin name) is strongly discouraged since it’s a reserved naming pattern for official Apache Maven plugins maintained by the Apache Maven team with groupId org.apache.maven.plugins . Using this naming pattern is an infringement of the Apache Maven Trademark.
Your First Plugin
In this section we will build a simple plugin with one goal which takes no parameters and simply displays a message on the screen when run. Along the way, we will cover the basics of setting up a project to create a plugin, the minimal contents of a Java mojo which will define goal code, and a couple ways to execute the mojo.
Your First Mojo
At its simplest, a Java mojo consists simply of a single class representing one plugin’s goal. There is no requirement for multiple classes like EJBs, although a plugin which contains a number of similar mojos is likely to use an abstract superclass for the mojos to consolidate code common to all mojos.
When processing the source tree to find mojos, plugin-tools looks for classes with either @Mojo Java 5 annotation or » goal » javadoc annotation. Any class with this annotation are included in the plugin configuration file.
A Simple Mojo
Listed below is a simple mojo class which has no parameters. This is about as simple as a mojo can be. After the listing is a description of the various parts of the source.
package sample.plugin; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; /** * Says "Hi" to the user. * */ @Mojo( name = "sayhi") public class GreetingMojo extends AbstractMojo < public void execute() throws MojoExecutionException < getLog().info( "Hello, world." ); >>
- The class org.apache.maven.plugin.AbstractMojo provides most of the infrastructure required to implement a mojo except for the execute method.
- The annotation » @Mojo » is required and control how and when the mojo is executed.
- The execute method can throw two exceptions:
- org.apache.maven.plugin.MojoExecutionException if an unexpected problem occurs. Throwing this exception causes a «BUILD ERROR» message to be displayed.
- org.apache.maven.plugin.MojoFailureException if an expected problem (such as a compilation failure) occurs. Throwing this exception causes a «BUILD FAILURE» message to be displayed.
All Mojo annotations are described by the Mojo API Specification.
Project Definition
Once the mojos have been written for the plugin, it is time to build the plugin. To do this properly, the project’s descriptor needs to have a number of settings set properly:
groupId This is the group ID for the plugin, and should match the common prefix to the packages used by the mojos artifactId This is the name of the plugin version This is the version of the plugin packaging This should be set to » maven-plugin « dependencies A dependency must be declared to the Maven Plugin Tools API to resolve » AbstractMojo » and related classes Listed below is an illustration of the sample mojo project’s pom with the parameters set as described in the above table:
4.0.0 sample.plugin hello-maven-plugin 1.0-SNAPSHOT maven-plugin Sample Parameter-less Maven Plugin org.apache.maven maven-plugin-api 3.0 provided org.apache.maven.plugin-tools maven-plugin-annotations 3.4 provided Building a Plugin
There are few plugins goals bound to the standard build lifecycle defined with the maven-plugin packaging:
compile Compiles the Java code for the plugin process-classes Extracts data to build the plugin descriptor test Runs the plugin’s unit tests package Builds the plugin jar install Installs the plugin jar in the local repository deploy Deploys the plugin jar to the remote repository Executing Your First Mojo
The most direct means of executing your new plugin is to specify the plugin goal directly on the command line. To do this, you need to configure the hello-maven-plugin plugin in you project:
.
sample.plugin hello-maven-plugin 1.0-SNAPSHOT And, you need to specify a fully-qualified goal in the form of:
mvn groupId:artifactId:version:goal
For example, to run the simple mojo in the sample plugin, you would enter » mvn sample.plugin:hello-maven-plugin:1.0-SNAPSHOT:sayhi » on the command line.
Tips: version is not required to run a standalone goal.
Shortening the Command Line
There are several ways to reduce the amount of required typing:
- If you need to run the latest version of a plugin installed in your local repository, you can omit its version number. So just use » mvn sample.plugin:hello-maven-plugin:sayhi » to run your plugin.
- You can assign a shortened prefix to your plugin, such as mvn hello:sayhi . This is done automatically if you follow the convention of using $-maven-plugin (or maven-$-plugin if the plugin is part of the Apache Maven project). You may also assign one through additional configuration — for more information see Introduction to Plugin Prefix Mapping.
- Finally, you can also add your plugin’s groupId to the list of groupIds searched by default. To do this, you need to add the following to your $/.m2/settings.xml file:
At this point, you can run the mojo with » mvn hello:sayhi «.
Attaching the Mojo to the Build Lifecycle
You can also configure your plugin to attach specific goals to a particular phase of the build lifecycle. Here is an example:
sample.plugin hello-maven-plugin 1.0-SNAPSHOT sample.plugin hello-maven-plugin compile sayhi This causes the simple mojo to be executed whenever Java code is compiled. For more information on binding a mojo to phases in the lifecycle, please refer to the Build Lifecycle document.
Mojo archetype
To create a new plugin project, you could using the Mojo archetype with the following command line:
mvn archetype:generate \ -DgroupId=sample.plugin \ -DartifactId=hello-maven-plugin \ -DarchetypeGroupId=org.apache.maven.archetypes \ -DarchetypeArtifactId=maven-archetype-plugin
Parameters
It is unlikely that a mojo will be very useful without parameters. Parameters provide a few very important functions:
- It provides hooks to allow the user to adjust the operation of the plugin to suit their needs.
- It provides a means to easily extract the value of elements from the POM without the need to navigate the objects.
Defining Parameters Within a Mojo
Defining a parameter is as simple as creating an instance variable in the mojo and adding the proper annotations. Listed below is an example of a parameter for the simple mojo:
/** * The greeting to display. */ @Parameter( property = "sayhi.greeting", defaultValue = "Hello World!" ) private String greeting;
The portion before the annotations is the description of the parameter. The parameter annotation identifies the variable as a mojo parameter. The defaultValue parameter of the annotation defines the default value for the variable. This value can include expressions which reference the project, such as » $ » (more can be found in the «Parameter Expressions» document). The property parameter can be used to allow configuration of the mojo parameter from the command line by referencing a system property that the user sets via the -D option.
Configuring Parameters in a Project
Configuring the parameter values for a plugin is done in a Maven project within the pom.xml file as part of defining the plugin in the project. An example of configuring a plugin:
sample.plugin hello-maven-plugin 1.0-SNAPSHOT Welcome In the configuration section, the element name (» greeting «) is the parameter name and the contents of the element (» Welcome «) is the value to be assigned to the parameter.
Note: More details can be found in the Guide to Configuring Plugins.
Using Setters
You are not restricted to using private field mapping which is good if you are trying to make you Mojos resuable outside the context of Maven. Using the example above we could name our private fields using the underscore convention and provide setters that the configuration mapping mechanism can use. So our Mojo would look like the following:
public class MyQueryMojo extends AbstractMojo < @Parameter(property="url") private String _url; @Parameter(property="timeout") private int _timeout; @Parameter(property="options") private String[] _options; public void setUrl( String url ) < _url = url; >public void setTimeout( int timeout ) < _timeout = timeout; >public void setOptions( String[] options ) < _options = options; >public void execute() throws MojoExecutionException < . >>
Note the specification of the property name for each parameter which tells Maven what setter and getter to use when the field’s name does not match the intended name of the parameter in the plugin configuration.
Resources
- Mojo Documentation: Mojo API, Mojo annotations
- Maven Plugin Testing Harness: Testing framework for your Mojos.
- Plexus: The IoC container used by Maven.
- Plexus Common Utilities: Set of utilities classes useful for Mojo development.
- Commons IO: Set of utilities classes useful for file/path handling.
- Common Bugs and Pitfalls: Overview of problematic coding patterns.
Разработка java система плагинов
Here I will show how to implement a plugin system for a Java application. I was using Gradle 4.0.2 and Intellij IDEA 2017.3 for this.
First, we’ll make a folder for our sources. Its name will be a top level project in our Gradle configuration, so type some general name, like «Plugin example». Then, navigate into this folder and open command line or terminal. Execute this command:
gradle init —type java-application
It will create necessary files and folders (including a Gradle wrapper) with a default layout. We will switch to using the generated wrapper next.
./gradlew build
It will download necessary files and build the project. Then, you can run the generated application stub by
./gradlew run
The app will run and exit. Then, open this project in IDEA and initialize it without changing any settings. Create a directory for running application. Create a run configuration of type «Application»: select the main class, set «Working directory» to the created folder, set «Use classpath of module» to «[project name]_main»; set the JRE version, skip the rest options, and apply. After this, we can start coding.
Create an interface which will represent a plugin. I named it just «Plugin». Next, we’ll need to create a subproject for «Plugin» implementation:
- Make a subdirectory «Implementation» of «Plugin example»;
- Create a «build.gradle» file in it;
- Create a folder «src/main/java» in this folder (standard Gradle source set);
- Write a line «include (‘Implementation’)» in the «settings.gradle»;
- We will change the main build file slightly, so the ‘java’ plugin and top project repositories are applied to all projects:
// Apply the java plugin to add support for Java allprojects < apply plugin: 'java' repositories < // You can declare any Maven/Ivy/file repository here. jcenter() >> // Apply the application plugin to add support for building an application apply plugin: 'application' dependencies < // This dependency is found on compile classpath of this component and consumers. compile 'com.google.guava:guava:21.0' // Use JUnit test framework testCompile 'junit:junit:4.12' >// Define the main class for the application mainClassName = 'App'
If all steps were performed correctly, you now can create an implementation of «Plugin» in the «Implementation» subproject. Let’s name it «Implementation» as well.
Add a method to Plugin interface:
And implement it in Implementation:
public class Implementation implements Plugin < @Override public boolean load() < System.out.println("Plugin loaded"); return true; >>
Now we must create a way to find and load the plugins. We’ll go with a classic approach — parsing a specific directory for jars. This folder will be named «plugins». The application will create it if it doesn’t exist or will scan it for jars and load ones that have a class implementing Plugin interface. The class will be instantiated via default constructor and its «load()» method will be called.
static final String PLUGIN_FOLDER="plugins"; public static void main(String[] args) < File pluginFolder=new File(PLUGIN_FOLDER); if(!pluginFolder.exists()) < if(pluginFolder.mkdirs()) < System.out.println("Created plugin folder"); >; > File[] files=pluginFolder.listFiles((dir, name) -> name.endsWith(".jar")); ArrayList urls=new ArrayList<>(); ArrayList classes=new ArrayList<>(); if(files!=null) < Arrays.stream(files).forEach(file -> < try < JarFile jarFile=new JarFile(file); urls.add(new URL("jar:file:"+PLUGIN_FOLDER+"/"+file.getName()+"!/")); jarFile.stream().forEach(jarEntry -> < if(jarEntry.getName().endsWith(".class")) < classes.add(jarEntry.getName()); >>); > catch (IOException e) < e.printStackTrace(); >>); URLClassLoader pluginLoader=new URLClassLoader(urls.toArray(new URL[urls.size()])); classes.forEach(s -> < try < Class classs=pluginLoader.loadClass(s.replaceAll("/",".").replace(".class","")); Class[] interfaces=classs.getInterfaces(); for (Class anInterface : interfaces) < if(anInterface==Plugin.class) < Plugin plugin= (Plugin) classs.newInstance(); if(plugin.load()) < System.out.println("Loaded plugin "+classs.getCanonicalName()+" successfully"); >break; > > > catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) < e.printStackTrace(); >>); > System.out.println("Application stopped."); >
As you can see, the application now gets all .jar files from «plugins» folder, creates a list of class URLs and names. Then a URLClassLoader is used to handle these URLs and load classes.
To see it in action, build the Implementation jar and place it into the «plugins» folder (assuming you ran the app once). The app should load Implementation and call its method, then exit.
There is a way to load .class files directly, but that approach will be covered separately.