基于maven-jar-plugin打造一款自动识别主类的maven打包插件
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,
15年
工作经验,精通Java编程
,高并发设计
,Springboot和微服务
,熟悉Linux
,ESXI虚拟化
以及云原生Docker和K8s
,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea
基于maven-jar-plugin打造一款自动识别主类的maven打包插件
引言
相信每个Java开发者都经历过这样的场景:新建一个可执行JAR项目时,总要花几分钟在pom.xml里翻找主类路径,然后小心翼翼地配置到maven-jar-plugin
里。更痛苦的是当项目有多个main
方法时,稍不留神就会打包失败。这种重复劳动不仅浪费时间,还容易埋下隐患。
在Java项目构建过程中,MANIFEST.MF
文件中的Main-Class
属性配置是一个关键但容易出错的环节。传统方式需要在pom.xml中显式声明主类路径,这不仅增加了维护成本,在大型多模块项目中更可能因配置遗漏导致运行时异常。Spring Boot通过@SpringBootApplication
注解实现自动识别主类的机制广受好评,但在非Spring Boot
项目中这种能力却难以直接复用。
本文将深入探讨如何通过开发mainclass-finder-maven-plugin
自定义插件,在Maven构建体系中实现智能主类识别与自动注入,既支持常规main
方法识别,也可通过指定注解灵活定位主类,最终无缝集成到标准maven-jar-plugin
打包流程中。这种方案不仅能提升构建效率,更实现了构建配置的智能化演进。
一、插件开发环境准备
1.1 创建Maven插件项目
首先新建一个标准的Maven项目,pom.xml需要包含以下核心依赖:
<!-- 插件核心依赖 -->
<dependencies><!-- Maven插件开发API --><dependency><groupId>org.apache.maven</groupId><artifactId>maven-plugin-api</artifactId><version>3.8.6</version></dependency><!-- 注解处理器 --><dependency><groupId>org.apache.maven.plugin-tools</groupId><artifactId>maven-plugin-annotations</artifactId><version>3.6.4</version><scope>provided</scope></dependency><!-- 秘密武器:Spring Boot的类扫描工具 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-loader-tools</artifactId><version>2.7.12</version></dependency>
</dependencies><!-- 插件打包配置 -->
<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-plugin-plugin</artifactId><version>3.6.4</version></plugin></plugins>
</build>
关键依赖说明:
maven-plugin-api
:插件开发的基础APImaven-plugin-annotations
:简化开发的注解支持spring-boot-loader-tools
:借用Spring Boot强大的类扫描能力
二、核心代码实现解析
2.1 Mojo类完整实现
这是我们插件的"大脑",所有魔法都发生在这里:
import java.io.File;
import java.io.IOException;import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.springframework.boot.loader.tools.MainClassFinder;
import com.sinhy.nature.utils.ObjectUtils;/*** 主类发现者插件核心实现* 在PROCESS_CLASSES阶段扫描类文件,智能识别主类* * @author lilinhai* @since 2025-04-20 09:49* @version V1.0*/
@Mojo(name = "find", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class MainClassFinderMojo extends AbstractMojo {// 类文件输出目录(默认target/classes)@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true)private File classesDirectory;// Maven项目上下文@Parameter(defaultValue = "${project}", readonly = true)private MavenProject project;/*** 主类属性名配置(可自定义)* 示例:存放到mainClassFoundByFinderPlugin属性*/@Parameter(defaultValue = "mainClassFoundByFinderPlugin")private String mainClassAssignmentAttributeName;/*** 按注解过滤主类(可选配置)* 示例:使用SpringBoot的启动注解*/@Parameterprivate String findByAnnotationOnMainClass;@Overridepublic void execute() throws MojoExecutionException, MojoFailureException {try {// 核心扫描逻辑String mainClass = detectMainClass();// 属性注入和配置修改configureJarPlugin(mainClass);} catch (IOException e) {throw new MojoFailureException("主类扫描失败", e);}}private String detectMainClass() throws IOException, MojoFailureException {String mainClass;// 根据是否配置注解决定扫描策略if (ObjectUtils.isEmpty(findByAnnotationOnMainClass)) {getLog().info("开始扫描标准main方法...");mainClass = MainClassFinder.findSingleMainClass(classesDirectory);} else {getLog().info("按注解[" + findByAnnotationOnMainClass + "]扫描主类...");mainClass = MainClassFinder.findSingleMainClass(classesDirectory, findByAnnotationOnMainClass);}if (mainClass == null) {throw new MojoFailureException("未找到符合条件的主类!检查:"+ (findByAnnotationOnMainClass != null ? "注解@" + findByAnnotationOnMainClass : "main方法"));}return mainClass;}private void configureJarPlugin(String mainClass) throws MojoExecutionException {// 将主类路径存入Maven属性池project.getProperties().setProperty(mainClassAssignmentAttributeName, mainClass);getLog().info("成功识别主类:" + mainClass);// 动态修改maven-jar-plugin配置Plugin jarPlugin = project.getBuild().getPluginsAsMap().get("org.apache.maven.plugins:maven-jar-plugin");if (jarPlugin == null) {throw new MojoExecutionException("请确保已配置maven-jar-plugin!");}// 构建或修改XML配置节点Xpp3Dom config = (Xpp3Dom) jarPlugin.getConfiguration();if (config == null) {config = new Xpp3Dom("configuration");jarPlugin.setConfiguration(config);}// 层级结构:configuration -> archive -> manifest -> mainClassXpp3Dom archiveNode = getOrCreateNode(config, "archive");Xpp3Dom manifestNode = getOrCreateNode(archiveNode, "manifest");Xpp3Dom mainClassNode = getOrCreateNode(manifestNode, "mainClass");// 智能设置值(优先保留用户配置)if (mainClassNode.getValue() == null || mainClassNode.getValue().contains("${")) {mainClassNode.setValue(mainClass);getLog().info("已自动配置maven-jar-plugin");}}// 辅助方法:获取或创建XML节点private Xpp3Dom getOrCreateNode(Xpp3Dom parent, String nodeName) {Xpp3Dom node = parent.getChild(nodeName);if (node == null) {node = new Xpp3Dom(nodeName);parent.addChild(node);}return node;}
}
代码亮点解读:
- 双模式扫描:既支持传统
main
方法,也支持注解标记 - 配置兼容:优先保留用户自定义配置
- 智能提示:通过
getLog()
输出构建日志 - 健壮性检查:对可能缺失的插件进行预校验
三、插件使用指南
3.1 基础配置
在需要使用的项目中添加:
<build><plugins><!-- 我们的智能插件 --><plugin><groupId>com.sinhy</groupId><artifactId>mainclass-finder-maven-plugin</artifactId><version>2.1.0</version><executions><execution><phase>process-classes</phase><goals><goal>find</goal></goals></execution></executions></plugin><!-- 标准打包插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifest><!-- 这里使用动态注入的属性 --><mainClass>${mainClassFoundByFinderPlugin}</mainClass></manifest></archive></configuration></plugin></plugins>
</build>
3.2 高级配置示例
当需要使用注解过滤时:
<plugin><groupId>com.sinhy</groupId><artifactId>mainclass-finder-maven-plugin</artifactId><configuration><findByAnnotationOnMainClass>org.springframework.boot.autoconfigure.SpringBootApplication</findByAnnotationOnMainClass><!-- 可选:自定义属性名 --><mainClassAssignmentAttributeName>autoDetectedMainClass</mainClassAssignmentAttributeName></configuration><!-- ...其余配置同上... -->
</plugin>
四、工作原理深度剖析
4.1 执行时机把控
我们将插件绑定到process-classes
阶段(即编译生成class文件后),这样就能:
- 确保扫描到最新编译的类
- 在打包前完成主类配置
4.2 主类扫描的底层逻辑
借助MainClassFinder
的两个核心方法:
// 扫描标准main方法
public static String findSingleMainClass(File directory)// 扫描带指定注解的类
public static String findSingleMainClass(File directory, String annotationClassName)
其内部实现原理是:
- 遍历目录下的所有.class文件
- 使用ASM解析字节码
- 检查是否包含main方法
- 验证类/方法修饰符是否符合规范
- 检查注解标记(如果配置了的话)
4.3 动态配置的奥秘
我们通过修改Maven项目的Properties和Plugin配置来实现动态注入:
// 设置全局属性
project.getProperties().setProperty("mainClassFoundByFinderPlugin", "com.example.Main");// 修改maven-jar-plugin的XML配置
<configuration><archive><manifest><mainClass>com.example.Main</mainClass></manifest></archive>
</configuration>
五、插件运行测试效果
如下图所示,是ddns应用项目成功的实践。图中显示已通过mainclass-finder-maven-plugin
插件成功自动获取到项目的主类,且成功注入到jar包中的MANIFEST.MF
文件中。
总结
经过这个插件的开发实践,我们不仅解决了具体的工程问题,更重要的是体会到了Maven插件生态的强大之处。当发现重复的配置工作时,不妨停下来想想:能不能通过自动化手段解决?
这个插件现在已经在我的团队内部使用了半年多,累计节省了数百小时的配置时间。希望它也能给你的项目带来便利,更期待你能在此基础上扩展出更强大的功能!