0

0

Java JAR清单文件自定义属性的正确添加与读取方法

花韻仙語

花韻仙語

发布时间:2025-08-06 15:18:15

|

473人浏览过

|

来源于php中文网

原创

java jar清单文件自定义属性的正确添加与读取方法

本教程旨在解决Java应用程序在修改现有JAR包清单文件(MANIFEST.MF)并添加自定义属性后,无法通过java.util.jar.Manifest类正确读取的问题。核心在于强调清单文件严格的格式要求,特别是每个属性行(包括最后一个)末尾必须跟随换行符,以确保Java虚拟机能够正确解析并加载这些自定义属性。

JAR清单文件(MANIFEST.MF)基础

JAR文件是Java平台常用的打包格式,而META-INF/MANIFEST.MF文件是JAR包的元数据文件,它包含了关于JAR包及其内容的各种信息,例如版本号、入口类、依赖关系等。清单文件采用键值对的形式组织信息,每个属性占用一行,并且遵循严格的格式规范。Java的java.util.jar.Manifest类负责解析这些清单文件,并提供对其中属性的访问。

程序化修改JAR清单文件

在某些场景下,我们可能需要动态地向现有JAR文件的清单中添加自定义属性,例如部署版本信息、配置参数等。直接修改JAR文件内部的MANIFEST.MF需要通过Java的NIO.2文件系统API来完成。

挂载JAR文件系统

Java 7引入的NIO.2文件系统API允许我们将JAR文件视为一个文件系统来操作,这使得我们可以像操作普通文件一样,读写JAR内部的文件。

Map<String, String> env = new HashMap<>();
env.put("create", "true"); // 如果JAR不存在则创建,这里我们是修改现有JAR
// 将JAR文件转换为URI,以便FileSystems可以识别为JAR文件系统
URI jarUri = jarFileToURI(jar);
try (FileSystem fileSystem = FileSystems.newFileSystem(jarUri, env)) {
    // 后续操作在try-with-resources块中进行,确保FileSystem正确关闭
    // ...
}

读取与修改清单内容

要修改清单文件,首先需要读取其现有内容。MANIFEST.MF通常是一个文本文件,我们可以将其内容读入内存,然后进行修改。

立即学习Java免费学习笔记(深入)”;

Path manifestPath = fileSystem.getPath("/META-INF/MANIFEST.MF");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Files.copy(manifestPath, byteArrayOutputStream);

// 将清单字节转换为字符串,并构建StringBuilder以便追加
StringBuilder manifestData = new StringBuilder(byteArrayOutputStream.toString().trim());

关键:确保正确的行终止符

这是导致自定义属性无法被Manifest类正确读取的常见陷阱。MANIFEST.MF文件的规范要求每个属性行,包括最后一个属性行,都必须以换行符( )结束。如果最后一个自定义属性之后没有换行符,Manifest解析器可能会将其忽略。

错误的添加方式(可能导致问题):

manifestData.append("
");
manifestData.append("Deployments-Version: ");
manifestData.append(testVersion); // 缺少末尾的换行符

正确的添加方式:

Spirit Me
Spirit Me

SpiritMe允许用户使用数字化身制作视频,这些化身可以模拟用户的声音和情感

下载
manifestData.append("
"); // 确保新属性前有换行
manifestData.append("Deployments-Version: ");
manifestData.append(testVersion);
manifestData.append("
"); // 关键:确保新属性后有换行,作为该行的终止符

将修改写回JAR

修改完成后,将更新后的清单内容写回JAR文件中的MANIFEST.MF路径。

Files.copy(new ByteArrayInputStream(manifestData.toString().getBytes()), manifestPath,
        StandardCopyOption.REPLACE_EXISTING);

程序化读取JAR清单属性

一旦JAR文件被修改并保存,我们可以通过java.util.jar.JarFile类来读取其清单属性。

使用JarFile类读取

JarFile类提供了getManifest()方法来获取JAR文件的Manifest对象。通过这个对象,我们可以访问主属性(Main Attributes)以及各个条目(Entries)的属性。

try (JarFile jarFile = new JarFile(jar)) {
    Manifest manifest = jarFile.getManifest();
    if (manifest != null) {
        // 获取主属性集合
        Attributes mainAttributes = manifest.getMainAttributes();
        // 通过键名获取自定义属性的值
        String deploymentVersion = mainAttributes.getValue("Deployments-Version");
        System.out.println("读取到的 Deployments-Version: " + deploymentVersion);
    } else {
        System.out.println("JAR文件没有清单或清单无法读取。");
    }
} catch (IOException e) {
    e.printStackTrace();
}

如果修改清单时没有遵循严格的格式(特别是缺少最后的换行符),即使通过文本编辑器或压缩工具(如7zip)能看到自定义属性,JarFile.getManifest()在解析时也可能无法识别该属性,导致getValue()返回null。

完整示例与运行验证

以下是一个完整的Java代码示例,演示了如何向现有JAR文件添加自定义清单属性并随后正确读取它们。

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

public class JarManifestModifier {

    public static void main(String[] args) throws IOException, URISyntaxException {
        // 假设JAR文件路径,请替换为您的实际路径
        File jar = new File("C:\Users\employee1234\Desktop\auth-0.1.3.jar");
        String testVersion = "1.2.3";
        String customAttributeName = "Deployments-Version";

        // 1. 修改JAR文件中的MANIFEST.MF
        System.out.println("--- 尝试修改JAR清单文件 ---");
        Map<String, String> env = new HashMap<>();
        env.put("create", "true"); // 允许创建文件系统

        try (FileSystem fileSystem = FileSystems.newFileSystem(jarFileToURI(jar), env)) {
            Path manifestPath = fileSystem.getPath("/META-INF/MANIFEST.MF");

            // 读取现有清单内容
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            Files.copy(manifestPath, byteArrayOutputStream);

            // 将清单字节转换为字符串并构建StringBuilder
            StringBuilder manifestData = new StringBuilder(byteArrayOutputStream.toString().trim());

            // 添加自定义清单属性,确保末尾有换行符
            // 注意:如果manifestData.toString().trim()结果是空字符串,则直接添加
            if (manifestData.length() > 0) {
                manifestData.append("
"); // 确保新属性前有换行
            }
            manifestData.append(customAttributeName).append(": ").append(testVersion).append("
"); // 关键:新属性行末尾必须有换行

            // 将修改后的清单内容写回JAR
            Files.copy(new ByteArrayInputStream(manifestData.toString().getBytes()), manifestPath,
                    StandardCopyOption.REPLACE_EXISTING);

            System.out.println("清单文件修改成功,添加属性: " + customAttributeName + ": " + testVersion);
        } catch (Exception e) {
            System.err.println("修改JAR清单文件失败: " + e.getMessage());
            e.printStackTrace();
            return; // 修改失败则退出
        }

        // 2. 尝试从修改后的JAR中读取自定义属性
        System.out.println("
--- 尝试从修改后的JAR中读取自定义属性 ---");
        try (JarFile jarFile = new JarFile(jar)) {
            Manifest manifest = jarFile.getManifest();
            if (manifest != null) {
                String retrievedVersion = manifest.getMainAttributes().getValue(customAttributeName);
                System.out.println("从JAR中读取到的 " + customAttributeName + ": " + retrievedVersion);
                if (testVersion.equals(retrievedVersion)) {
                    System.out.println("自定义属性读取成功!");
                } else {
                    System.out.println("自定义属性读取失败或值不匹配!");
                }
            } else {
                System.out.println("JAR文件没有清单或清单无法读取。");
            }
        } catch (IOException e) {
            System.err.println("读取JAR清单文件失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // 辅助方法:将File对象转换为JAR文件系统URI
    private static URI jarFileToURI(File jarFile) throws URISyntaxException {
        String sp = slashify(jarFile.getAbsoluteFile().getPath(), false);
        if (sp.startsWith("//"))
            sp = "//" + sp;
        return new URI("jar:file", null, sp, null);
    }

    // 辅助方法:将路径转换为斜杠分隔格式
    private static String slashify(String path, boolean isDirectory) {
        String p = path;
        if (File.separatorChar != '/')
            p = p.replace(File.separatorChar, '/');
        if (!p.startsWith("/"))
            p = "/" + p;
        if (!p.endsWith("/") && isDirectory)
            p = p + "/";
        return p;
    }
}

运行前请注意:

  • 将C:\Users\employee1234\Desktop\auth-0.1.3.jar替换为您的实际JAR文件路径。
  • 此操作会直接修改您的JAR文件,建议在运行前备份原始JAR文件。

注意事项与最佳实践

  1. 清单文件格式的严格性:MANIFEST.MF文件对格式非常敏感。除了本文强调的行终止符,还包括:
    • 每行最大长度通常为72字节(超出部分需要通过空格缩进继续)。
    • 键值对之间必须用冒号和空格分隔(Key: Value)。
    • 属性名不区分大小写,但通常建议保持一致性。
  2. 错误处理:在实际应用中,务必对文件操作和JAR解析过程中可能出现的IOException进行适当的异常处理。
  3. 调试技巧:如果自定义属性仍然无法读取,可以使用文本编辑器(如Notepad++、VS Code)或压缩工具(如7-Zip、WinRAR)打开JAR文件,检查META-INF/MANIFEST.MF的内容,确认自定义属性是否存在且格式正确(特别是末尾的换行符)。
  4. 避免冲突:自定义属性的名称应避免与标准清单属性(如Manifest-Version、Created-By、Main-Class等)冲突。
  5. 替代方案:对于更复杂的JAR操作或构建流程,可以考虑使用构建工具(如Maven、Gradle)或专门的库(如Apache Ant的JAR任务),它们通常提供了更健壮和易用的API来管理JAR文件及其清单。

通过理解并遵循MANIFEST.MF文件的严格格式要求,特别是确保每个属性行(包括最后一个)都以换行符结束,可以有效解决Java应用程序在动态修改JAR清单后无法正确读取自定义属性的问题。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Java Maven专题
Java Maven专题

本专题聚焦 Java 主流构建工具 Maven 的学习与应用,系统讲解项目结构、依赖管理、插件使用、生命周期与多模块项目配置。通过企业管理系统、Web 应用与微服务项目实战,帮助学员全面掌握 Maven 在 Java 项目构建与团队协作中的核心技能。

0

2025.09.15

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1132

2024.03.01

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

931

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

32

2025.12.06

apache是什么意思
apache是什么意思

Apache是Apache HTTP Server的简称,是一个开源的Web服务器软件。是目前全球使用最广泛的Web服务器软件之一,由Apache软件基金会开发和维护,Apache具有稳定、安全和高性能的特点,得益于其成熟的开发和广泛的应用实践,被广泛用于托管网站、搭建Web应用程序、构建Web服务和代理等场景。本专题为大家提供了Apache相关的各种文章、以及下载和课程,希望对各位有所帮助。

422

2023.08.23

apache启动失败
apache启动失败

Apache启动失败可能有多种原因。需要检查日志文件、检查配置文件等等。想了解更多apache启动的相关内容,可以阅读本专题下面的文章。

939

2024.01.16

Java 流式处理与 Apache Kafka 实战
Java 流式处理与 Apache Kafka 实战

本专题专注讲解 Java 在流式数据处理与消息队列系统中的应用,系统讲解 Apache Kafka 的基础概念、生产者与消费者模型、Kafka Streams 与 KSQL 流式处理框架、实时数据分析与监控,结合实际业务场景,帮助开发者构建 高吞吐量、低延迟的实时数据流管道,实现高效的数据流转与处理。

181

2026.02.04

C++多线程并发控制与线程安全设计实践
C++多线程并发控制与线程安全设计实践

本专题围绕 C++ 在高性能系统开发中的并发控制技术展开,系统讲解多线程编程模型与线程安全设计方法。内容包括互斥锁、读写锁、条件变量、原子操作以及线程池实现机制,同时结合实际案例分析并发竞争、死锁避免与性能优化策略。通过实践讲解,帮助开发者掌握构建稳定高效并发系统的关键技术。

2

2026.03.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
RunnerGo从入门到精通
RunnerGo从入门到精通

共22课时 | 1.8万人学习

尚学堂Mahout视频教程
尚学堂Mahout视频教程

共18课时 | 3.3万人学习

Linux优化视频教程
Linux优化视频教程

共14课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号