Maven plugin can't load class
Asked Answered
A

5

19

I'm trying to make a maven plugin that needs to use reflection. I want a project to run the plugin, and give it the full name of a class in the project, and the plugin will load it by reflection to get info from it.

There's something strange with the classloader though, because it can't find the class when I use

Class.forName("package.MyClass");

Looking at the "Guide to Maven Classloading", I can't quite figure out if my plugin's classloader, when being run in a different project, has access to that project's classes.

Applause answered 16/5, 2009 at 4:40 Comment(0)
A
19

I'm sure there's a better way, but here's how I got it to work:

Add the following to the javadoc at the top of your mojo: @requiresDependencyResolution runtime

Add a MavenProject parameter:

/**
 * @parameter expression="${project}"
 * @required
 * @readonly
 */
private MavenProject project;

Then you can get the dependencies at runtime, and make your own classloader:

List runtimeClasspathElements = project.getRuntimeClasspathElements();
URL[] runtimeUrls = new URL[runtimeClasspathElements.size()];
for (int i = 0; i < runtimeClasspathElements.size(); i++) {
  String element = (String) runtimeClasspathElements.get(i);
  runtimeUrls[i] = new File(element).toURI().toURL();
}
URLClassLoader newLoader = new URLClassLoader(runtimeUrls,
  Thread.currentThread().getContextClassLoader());

Then you can load your class using this new classloader:

Class bundle = newLoader.loadClass("package.MyClass");
Applause answered 19/5, 2009 at 14:44 Comment(1)
Note: When creating your custom ClassLoader, do not make the current ClassLoader its parent loader. If you do, a class that's in a dependency of your plugin may not be able to load a class that's only visible to your custom ClassLoader because it may resolve the class using the ClassLoader that loaded it, which will be the Maven ClassRealm loader, not your loader.Walkover
T
13

You should consider using this to add the runtime class path elements to the current class realm. (You can use the PluginDescriptor to retrieve the class realm.

List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
ClassRealm realm = descriptor.getClassRealm();

for (String element : runtimeClasspathElements)
{
    File elementFile = new File(element);
    realm.addURL(elementFile.toURI().toURL());
}

This worked perfectly for me!

As Dave asked, here is the way to get the PluginDescriptor:

/**
 * The plugin descriptor
 * 
 * @parameter default-value="${descriptor}"
 */
private PluginDescriptor descriptor;
Tsarism answered 7/2, 2011 at 13:2 Comment(5)
how do you get a handle to PluginDescriptor from inside the execute method of MyMojo?Beeline
Hey Dave, I adjusted my answer, hopefully it helps you. (You don't need a setter, as this is set via reflections afair).Tsarism
This is an excellent answer. It's along the same lines as the accepted answer, but more fully groks Maven's ClassRealm stuff. (No slight on the accepted answer, as that had the definite benefit of being 2 years early.)Sudor
The accepted answer didn't work for me with Maven 3.2.5+, put this worked perfectly. Would love to find more documentation on Maven's ClassRealm.Strick
As explained in https://mcmap.net/q/633831/-maven-plugin-can-39-t-load-class, nowadays, you have to use @Component to inject PluginDescriptor.Masturbation
L
7

I ran across this exact issue, today. The above suggestions didn't work for me, thought I would submit my solution to the list. I used the HibernateExporter mojo source which can be viewed at: http://grepcode.com/file/repo1.maven.org/maven2/org.codehaus.mojo/hibernate3-maven-plugin/2.2/org/codehaus/mojo/hibernate3/HibernateExporterMojo.java?av=f

/**
 * @parameter expression="${project}"
 * @required
 * @readonly
 */
private MavenProject project;

private ClassLoader getClassLoader() throws MojoExecutionException
{
  try
  {
    List<String> classpathElements = project.getCompileClasspathElements();
    classpathElements.add(project.getBuild().getOutputDirectory() );
    classpathElements.add(project.getBuild().getTestOutputDirectory() );
    URL urls[] = new URL[classpathElements.size()];

    for ( int i = 0; i < classpathElements.size(); ++i )
    {
      urls[i] = new File( (String) classpathElements.get( i ) ).toURI().toURL();
    }
    return new URLClassLoader(urls, getClass().getClassLoader() );
  }
  catch (Exception e)//gotta catch em all
  {
    throw new MojoExecutionException("Couldn't create a classloader.", e);
  }
}

public void execute() throws MojoExecutionException
{
  ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
  Thread.currentThread().setContextClassLoader(getClassLoader());

   //... your code here ...
}

Also make sure you are using the right MavenProject class. add this to your pom

<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-core</artifactId>
  <version>3.0.3</version>
</dependency>

<dependency>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-plugin-api</artifactId>
  <version>3.0.3</version>
</dependency>
Labrecque answered 4/11, 2012 at 15:37 Comment(0)
S
2

This worked for me and maven3 to get dependencies into the plugin classpath.

The trick is to use @Component to inject the PluginDescriptor. Otherwise it will not be set up correctly.

@Component
private MavenProject project;
@Component
private PluginDescriptor descriptor;

private void addDependenciesToClasspath(String artifactId) {
    for (Artifact artifact : project.getDependencyArtifacts()) {
        if (artifact.getArtifactId().equals(artifactId)) {
            try {
                final URL url = artifact.getFile().toURI().toURL();
                final ClassRealm realm = descriptor.getClassRealm();
                realm.addURL(url);
            }
            catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
Sloganeer answered 19/3, 2015 at 14:43 Comment(0)
Y
0

It's ok, we need to make our own classloaders Custom Classloaders

Yokefellow answered 16/4, 2021 at 6:53 Comment(1)
Link only answers are discouraged here. Links may break over time, rendering your answer useless. Please include a summary of what your answer does and why it may be better than the other answers already provided.Locksmith

© 2022 - 2024 — McMap. All rights reserved.