基于maven-jar-plugin打造一款自动识别主类的maven打包插件

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生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打包流程中。这种方案不仅能提升构建效率,更实现了构建配置的智能化演进。


一、插件开发环境准备

插件名:mainclass-finder-maven-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:插件开发的基础API
  • maven-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;}
}

代码亮点解读:

  1. 双模式扫描:既支持传统main方法,也支持注解标记
  2. 配置兼容:优先保留用户自定义配置
  3. 智能提示:通过getLog()输出构建日志
  4. 健壮性检查:对可能缺失的插件进行预校验

三、插件使用指南

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文件后),这样就能:

  1. 确保扫描到最新编译的类
  2. 在打包前完成主类配置

4.2 主类扫描的底层逻辑

借助MainClassFinder的两个核心方法:

// 扫描标准main方法
public static String findSingleMainClass(File directory)// 扫描带指定注解的类
public static String findSingleMainClass(File directory, String annotationClassName)

其内部实现原理是:

  1. 遍历目录下的所有.class文件
  2. 使用ASM解析字节码
  3. 检查是否包含main方法
  4. 验证类/方法修饰符是否符合规范
  5. 检查注解标记(如果配置了的话)

4.3 动态配置的奥秘

我们通过让mainclass-finder-maven-plugin插件去修改Maven项目的PropertiesPlugin配置来实现动态注入:

// 设置全局属性
project.getProperties().setProperty("mainClassFoundByFinderPlugin", "com.example.Main");// mainclass-finder-maven-plugin插件修改maven-jar-plugin的配置后的新XML配置
<configuration><archive><manifest><mainClass>com.example.Main</mainClass></manifest></archive>
</configuration>

五、插件运行测试效果

如下图所示,是ddns应用项目成功的实践。图中显示已通过mainclass-finder-maven-plugin插件成功自动获取到项目的主类,且成功注入到jar包中的MANIFEST.MF文件中。
在这里插入图片描述

总结

经过这个插件的开发实践,我们不仅解决了具体的工程问题,更重要的是体会到了Maven插件生态的强大之处。当发现重复的配置工作时,不妨停下来想想:能不能通过自动化手段解决?

这个插件现在已经在我的团队内部使用了半年多,累计节省了数百小时的配置时间。希望它也能给你的项目带来便利,更期待你能在此基础上扩展出更强大的功能!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/902186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多态的主要好处与不足

多态是面向对象编程的核心特性之一&#xff0c;它通过方法重写、接口实现等方式实现“同一操作作用于不同对象时产生不同行为”。以下是多态的主要好处与不足&#xff1a; 多态的好处 1. 提高代码灵活性和扩展性 开闭原则支持&#xff1a;新增子类时&#xff0c;无需修改现有…

excel解析图片pdf附件不怕

背景 工作中肯定会有导入excel还附带图片附件的下面是我解析的excel&#xff0c;支持图片、pdf、压缩文件实现 依次去解析excel&#xff0c;看看也没有附件&#xff0c;返回的格式是Map&#xff0c;key是第几行&#xff0c;value是附件list附件格式都被解析成pdf格式Reader.jav…

python爬虫 线程,进程,协程

0x00 线程 线程是一个执行单位&#xff0c;是在一个进程里面的&#xff0c;是共享进程里面的提供的内存等资源&#xff0c;使用多个线程时和使用多个进程相比&#xff0c;多个线程使用的内存等资源较少。进程像一座“房子”&#xff08;独立资源&#xff09;&#xff0c;线程是…

ES|QL,知道吗,专为搜索而生 —— 推出评分和语义搜索

作者&#xff1a;来自 Elastic Ioana Tagirta 在 Elasticsearch 8.18 和 9.0 中&#xff0c;ES|QL 支持评分、语义搜索以及更多的 match 函数配置选项&#xff0c;还有一个新的 KQL 函数。 使用 ES|QL 搜索 在 Elasticsearch 8.18 和 9.0 中&#xff0c;ES|QL 增加了一系列新功…

MIT6.S081-lab4

MIT6.S081-lab4 注&#xff1a;本篇lab的前置知识在《MIT6.S081-lab3前置》 1. RISC-V assembly 第一个问题 Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf? 我们先来看看main干了什么&#xff1a; …

一文总结通信电路中LC谐振回路中各公式以及对深入解读品质因数Q

目录 前言 一、基本公式总结 1.并联谐振回路 2.串联谐振回路 二、浅谈品质因数 1.衡量谐振回路能量存储与能量损耗之比的无量纲参数&#xff0c;用于描述谐振电路的频率选择性 2.当受到振荡驱动力时&#xff0c;谐振腔的中心频率与其带宽的比值 3.为什么谐振时电容上的…

Linux:文件系统

一.认识硬件–磁盘 1. 物理结构 1.2 存储结构 ❓如何定位⼀个扇区呢&#xff1f; 可以先定位磁头&#xff08;header&#xff09;——》确定磁头要访问哪⼀个柱⾯(磁道)&#xff08;cylinder&#xff09;——》 定位⼀个扇区(sector)。 柱⾯&#xff08;cylinder&#xff09…

数字孪生废气处理工艺流程

图扑数字孪生废气处理工艺流程系统。通过精准 3D 建模&#xff0c;对废气收集、预处理、净化、排放等全流程进行 1:1 数字化复刻&#xff0c;实时呈现设备运行参数、污染物浓度变化等关键数据。 借助图扑可视化界面&#xff0c;管理者可直观掌握废气处理各环节状态&#xff0c…

Scratch——第18课 列表接龙问题

在四级的考级中&#xff0c;接龙的题目虽然在CIE中只出现过两次&#xff0c;但是这类题目对字符串的知识点考察相对全面。 一、接龙游戏的判断方法 接龙的内容对应的字符数 ? 已接龙内容的字符数 满足条件>接龙内容的第一个字符数 ? 上一项接龙的最后一个字符 满足条件…

webgl入门实例-向量在图形学中的核心作用

在图形学中&#xff0c;向量是描述几何、光照、运动等核心概念的基础工具。以下是向量在图形学中的关键应用和深入解析&#xff1a; 1. 向量的核心作用 几何表示&#xff1a;描述点、方向、法线、切线等。空间变换&#xff1a;平移、旋转、缩放等操作依赖向量运算。光照计算&a…

Redis 是如何保证线程安全的?

Redis 是如何保证线程安全的&#xff1f; Redis 是一个高性能的键值数据库&#xff0c;广泛应用于缓存、消息队列、实时分析等场景。由于其性能优势&#xff0c;Redis 已经成为许多系统的核心组件之一。然而&#xff0c;很多开发者在使用 Redis 时&#xff0c;常常会问&#x…

Img2img-turbo 在2080Ti上的测试笔记

1. 介绍 [img2img-turbo]是[pytorch-CycleGAN-and-pix2pix]推荐的更新的图像变换的代码实现&#xff1b; 2. 配置信息 Conda环境名称&#xff1a;img2img-turbo 3. 问题描述 当前在我们尝试使用了官方推荐的训练命令在2080Ti上进行训练&#xff0c; 3.1 出现了 CUDA out …

代码随想录算法训练营第三十五天|416. 分割等和子集、698.划分为k个相等的子集、473.火柴拼正方形

今日题目 416. 分割等和子集 题目链接&#xff1a;416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 思考&#xff1a;本题要将数组分为两个子数组&#xff0c;且两个子数组和相等&#xff0c;因此首先可以想到的条件就是数组可分为两个&#xff0c;这要求数组元素数…

纯CSS实现自动滚动到底部

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>自动滚动到底部</title><style>*…

【新人系列】Golang 入门(十五):类型断言

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12898955.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Golang 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

AI大模型发展现状与MCP协议诞生的技术演进

1. 大模型能力边界与用户痛点&#xff08;2023年&#xff09; 代表模型&#xff1a;GPT-4&#xff08;OpenAI&#xff09;、Claude 3&#xff08;Anthropic&#xff09;、通义千问&#xff08;阿里云&#xff09;等展现出强大的生成能力&#xff0c;但存在明显局限&#xff1a…

深入理解Linux中的线程控制:多线程编程的实战技巧

个人主页&#xff1a;chian-ocean 文章专栏-Linux 前言&#xff1a; POSIX线程&#xff08;Pthreads&#xff09; 是一种在 POSIX 标准下定义的线程库&#xff0c;它为多线程编程提供了统一的接口&#xff0c;主要用于 UNIX 和类 UNIX 系统&#xff08;如 Linux、MacOS 和 BS…

(mac)Grafana监控系统之监控Linux的Redis

Grafana安装-CSDN博客 普罗米修斯Prometheus监控安装&#xff08;mac&#xff09;-CSDN博客 1.Redis_exporter安装 直接下载 wget https://github.com/oliver006/redis_exporter/releases/download/v1.0.3/redis_exporter-v1.0.3.linux-amd64.tar.gz 解压 tar -xvf redis_…

鸿蒙应用元服务开发-Account Kit未成年人模式订阅和处理用户信息变更

一、概述 通过订阅用户信息变更&#xff0c;您可以接收有关用户及其账户的重要更新。当用户取消元服务的授权信息、注销华为账号时&#xff0c;华为账号服务器会发送通知到元服务&#xff0c;元服务可以根据通知消息进行自身业务处理。 二、用户信息变更事件介绍 三、订阅用…

buildroot构建根文件系统报错(已解决大部分问题)

title: buildroot构建根文件系统报错(set FORCE_UNSAFE_CONFIGURE1) author: cbus categories: 小知识 tags:小知识 abbrlink: 53691 date: 2025-04-20 08:03:00 错误1 set FORCE_UNSAFE_CONFIGURE1 在使用buildroot构建根文件系统时&#xff0c;一切按照文档的配置&#xff0…