如果你真的需要通过 maven 或脚本来实现这一点,这就是我的工作方式。
基于 Stackoverflow 上另一个答案建议的方法,我实现了以下简单类:
package com.sample;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Scanner;
import org.clapper.util.classutil.ClassFilter;
import org.clapper.util.classutil.ClassFinder;
import org.clapper.util.classutil.ClassInfo;
public class MainScan {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Missing options");
System.exit(-1);
}
System.out.println("Filtering by: " + args[1]);
ClassFinder finder = new ClassFinder();
finder.addClassPath();
loadClasspath(finder, args[0]);
ClassFilter filter = new ImplementInterfaceFilter(args[1]);
// you could also use as a filter: new
// SubclassClassFilter(AbstractFileFilter.class);
// or make a concatenation of filters using an AndClassFilter
Collection<ClassInfo> foundClasses = new ArrayList<ClassInfo>();
finder.findClasses(foundClasses, filter);
if (foundClasses.size() > 0) {
for (ClassInfo classInfo : foundClasses) {
System.out.println("- " + classInfo.getClassName());
// consider also using classInfo.getClassLocation() to get the
// jar file providing it
}
} else {
System.out.println("No matches found.");
}
}
static void loadClasspath(ClassFinder finder, String file) throws IOException {
Scanner s = new Scanner(new File(file));
s.useDelimiter(File.pathSeparator);
try {
while (s.hasNext()) {
finder.add(new File(s.next()));
}
} finally {
s.close();
}
}
static class ImplementInterfaceFilter implements ClassFilter {
private String interfaceName;
public <T> ImplementInterfaceFilter(String name) {
this.interfaceName = name;
}
public boolean accept(ClassInfo info, ClassFinder finder) {
for (String i : info.getInterfaces()) {
if (i.endsWith(this.interfaceName)) {
return true;
}
}
return false;
}
}
}
请注意,该类位于com.sample
包中,但显然可以将其移动到其他包中。main 方法需要两个选项,一个类路径文件和一个接口名称,然后它将类路径添加到类路径查找器并扫描它以查找实现提供的接口名称的类(通过上面还提供的自定义过滤器)。Maven 将在运行时提供这两个选项,如下所示:
我使用这个库进行类路径扫描,因此正如其官方页面上所建议的,我们需要向我们的 POM 添加一个自定义存储库:
<repositories>
<repository>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</releases>
<id>clapper-org-maven-repo</id>
<name>org.clapper Maven Repo</name>
<url>http://maven.clapper.org/</url>
<layout>default</layout>
</repository>
</repositories>
以及所需的依赖项:
<dependencies>
...
<dependency>
<groupId>org.clapper</groupId>
<artifactId>javautil</artifactId>
<version>3.1.2</version>
</dependency>
...
</dependencies>
然后我们只需要在我们的 Maven 构建中配置以下内容:
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>build-classpath</goal>
</goals>
<configuration>
<outputFile>${project.build.directory}/classpath.txt</outputFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.sample.MainScan</mainClass>
<arguments>
<argument>${project.build.directory}/classpath.txt</argument>
<argument>${interfaceName}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
我们基本上是在配置 Maven 依赖插件以将完整的 Maven 构建类路径写入文件,然后使用 Exec Maven 插件来执行我们的自定义 Java 主程序,将类路径文件和参数传递给它${interfaceName}
。两个插件的执行都链接到validate
阶段:我们不需要执行完整的 Maven 构建,我们只需为此任务调用它的第一个阶段之一。
因此,我们可以调用 Maven 构建,如下所示:
mvn validate -DinterfaceName=Serializable -q
并有如下输出:
Filtering by: Serializable
- org.apache.commons.io.ByteOrderMark
- org.apache.commons.io.comparator.CompositeFileComparator
- org.apache.commons.io.comparator.DefaultFileComparator
...
Maven 命令将直接调用我们关注的阶段,validate
使用-q
选项(相当)跳过任何 Maven 构建日志,只得到我们感兴趣的输出。此外,我们可以通过-DinterfaceName=<value_here>
选项动态传递我们想要的接口。它将值传递给 Exec Maven 插件,并因此传递给上面的 Java 主程序。
根据进一步的需要(脚本、输出、格式等),可以很容易地调整 Java 主程序。此外,插件、依赖项、存储库配置也可以移动到 Maven 配置文件中,以使其更清晰、更有条理。
最后注意:如果您更改了上面的 Java main 的包,请不要忘记相应地更改 Exec Maven 插件配置(mainClass
元素)。
所以,回到你的问题:
- 是否可以通过 mvn 命令查询哪个包中的哪些类实现了选定的接口?是的,应用上面的方法。
- 甚至更多 - 在应用程序构建 CLASSPATH 中查找类和包,它是所选类的子类或超类?是的,看看同一个库中的SubclassClassFilter,相应地更改上面的 main,你就会得到它。
- 是否有适合我需要的插件?我找不到任何东西,但上面的代码可以很容易地转换成一个新的 Maven 插件。否则,这里描述的方法是 Java 代码和现有 Maven 插件使用的混合,无论如何都可以满足您的需要。