需求背景:写了一个实时读取日志文件以及监控的小程序,打包成了Jar包可执行文件,通过我们的web主系统上传到各个服务器,然后调用ssh命令执行。每次上传前都要通过解压缩软件修改或者替换里面的配置文件,这样感觉有点麻烦,就想办法能不能通过程序动态生成配置文件,然后修改或者替换掉Jar包里的配置文件,最后再上传到各个服务器去执行。

实现历程:刚开始看了大量文章,整理出来了一个操作Jar包的工具类,用工具类里面的方法去修改一个30M左右的Jar包文件时,发现耗时竟然要7秒,而且修改Jar文件的方法确实有点复杂(这个可能需要开发JDK的专业人士提供简单、高效的方法了),果断忍受不了,然后想能不能通过其他的方法来实现。想到了一个方案,就是不通过java去修改Jar里的文件,而是用linux的Jar命令(当然也是通过java去调用linux命令),先解压缩jar包,然后将动态生成的配置文件替换解压缩包里的配置文件,最后再将目录压缩成Jar文件。经过测试,这样大概需要4秒的时间,确实快了不少。想了很久也没有想到其他更好的办法,正打算实施的时候,看到了这条命令:jar uf test.jar manifest.mf  -u 更新已存在的 JAR 文件包 (添加文件到 JAR 文件包中)  -f 指定 JAR 文件名;瞬间来了灵感,直接调用这条命令不就OK了!通过这条命令几秒钟就轻轻松松搞定了,简直是山穷水尽疑无路,柳暗花明又一村。

下面是我整理的操作Jar包的工具类(已经通过测试,可以拿去直接用),包括读取Jar包的文件内容,遍历 Jar包,修改 Jar包文件内容等操作,供大家参考。

package com.agent.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream; public class JarUtil { /**
* 读取jar包所有的文件内容,显示JAR文件内容列表
* @param jarFileName
* @throws IOException
*/
public static void readJARList(String jarFilePath) throws IOException {
// 创建JAR文件对象
JarFile jarFile = new JarFile(jarFilePath);
// 枚举获得JAR文件内的实体,即相对路径
Enumeration en = jarFile.entries();
System.out.println("文件名\t文件大小\t压缩后的大小");
// 遍历显示JAR文件中的内容信息
while (en.hasMoreElements()) {
// 调用方法显示内容
process(en.nextElement());
}
} // 显示对象信息
private static void process(Object obj) {
// 对象转化成Jar对象
JarEntry entry = (JarEntry) obj;
// 文件名称
String name = entry.getName();
// 文件大小
long size = entry.getSize();
// 压缩后的大小
long compressedSize = entry.getCompressedSize();
System.out.println(name + "\t" + size + "\t" + compressedSize);
} /**
* 读取jar包里面指定文件的内容
* @param jarFileName jar包文件路径
* @param fileName 文件名
* @throws IOException
*/
public static void readJarFile(String jarFilePath,String fileName) throws IOException{
JarFile jarFile = new JarFile(jarFilePath);
JarEntry entry = jarFile.getJarEntry(fileName);
InputStream input = jarFile.getInputStream(entry);
readFile(input);
jarFile.close();
} public static void readFile(InputStream input) throws IOException{
InputStreamReader in = new InputStreamReader(input);
BufferedReader reader = new BufferedReader(in);
String line ;
while((line = reader.readLine())!=null){
System.out.println(line);
}
reader.close();
} /**
* 读取流
*
* @param inStream
* @return 字节数组
* @throws Exception
*/
public static byte[] readStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
return outSteam.toByteArray();
} /**
* 修改Jar包里的文件或者添加文件
* @param jarFile jar包路径
* @param entryName 要写的文件名
* @param data 文件内容
* @throws Exception
*/
public static void writeJarFile(String jarFilePath,String entryName,byte[] data) throws Exception{ //1、首先将原Jar包里的所有内容读取到内存里,用TreeMap保存
JarFile jarFile = new JarFile(jarFilePath);
//可以保持排列的顺序,所以用TreeMap 而不用HashMap
TreeMap tm = new TreeMap();
Enumeration es = jarFile.entries();
while(es.hasMoreElements()){
JarEntry je = (JarEntry)es.nextElement();
byte[] b = readStream(jarFile.getInputStream(je));
tm.put(je.getName(),b);
} JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFilePath));
Iterator it = tm.entrySet().iterator();
boolean has = false; //2、将TreeMap重新写到原jar里,如果TreeMap里已经有entryName文件那么覆盖,否则在最后添加
while(it.hasNext()){
Map.Entry item = (Map.Entry) it.next();
String name = (String)item.getKey();
JarEntry entry = new JarEntry(name);
jos.putNextEntry(entry);
byte[] temp ;
if(name.equals(entryName)){
//覆盖
temp = data;
has = true ;
}else{
temp = (byte[])item.getValue();
}
jos.write(temp, 0, temp.length);
} if(!has){
//最后添加
JarEntry newEntry = new JarEntry(entryName);
jos.putNextEntry(newEntry);
jos.write(data, 0, data.length);
}
jos.finish();
jos.close(); } /**
* 测试案例
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception{ //
readJarFile("D:\\esjavaclient-0.0.1-SNAPSHOT.jar","es-info.properties"); String data = "helloBabydsafsadfasdfsdafsdgasdgweqtqwegtqwfwefasdfasfadfasf"; long start = System.currentTimeMillis(); writeJarFile("D:\\esjavaclient-0.0.1-SNAPSHOT.jar","es-info.properties",data.getBytes()); long end = System.currentTimeMillis(); System.out.println(end-start); readJarFile("D:\\esjavaclient-0.0.1-SNAPSHOT.jar","es-info.properties"); } }

上面有个测试案例,测试读取其中一个配置文件,然后修改这个文件,最后再读取出来。 一个30M左右的Jar包文件耗时7秒多,实在忍受不了。网上找了很多文章,同时看了Zip相关的源码,也木有找到可以直接修改Jar包文件内容的方法。 最后只能另辟蹊径,找到了一个执行效率非常快的方法。

关于工具类没有删除文件的方法,大家根据修改的方法稍微改造一下即可。

工具类参考的博客:

http://blog.csdn.net/young_kim1/article/details/50482398

http://javasam.iteye.com/blog/1486803

如何快速修改Jar包里的文件内容:

贴出来我巧妙实现的代码:(如果不知道Java 如何调用linux命令的同学,请先补习一下这方面的知识)

        String cmd = "jar uf esjavaclient-0.0.1-SNAPSHOT.jar config.properties";
String[] cmds = {"/bin/sh","-c",cmd};
Process pro;
try {
pro = Runtime.getRuntime().exec(cmds);
pro.waitFor();
InputStream in = pro.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(in));
String line = null;
while((line = read.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

通过执行这条条命令,就可以很快将我们生成的配置文件config.properties覆盖掉Jar里的文件,从而达到修改的目的。

总结:如果Jar包里的文件不大的话,完全可以用工具类提供的方法去操作Jar包。比较大的话,修改Jar包的方法,推荐用我那个巧妙的方法。

最后:给开发JDK的专业人士提点建议,关于操作压缩包这方面的API还不够强大,希望后续可以完善一下

Java 操作jar包工具类以及如何快速修改Jar包里的文件内容的更多相关文章

  1. 最全的Java操作Redis的工具类,使用StringRedisTemplate实现,封装了对Redis五种基本类型的各种操作!

    转载自:https://github.com/whvcse/RedisUtil 代码 ProtoStuffSerializerUtil.java import java.io.ByteArrayInp ...

  2. Java操作字符串的工具类

    操作字符串的工具类 import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStre ...

  3. Java操作图片的工具类

    操作图片的工具类: import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.a ...

  4. java操作数组的工具类-Arrays

    static int binarySearch(type[] a, type key) 使用二分搜索法来搜索key元素在数组中的索引:若a数组不包括key,返回负数.(该方法必须已按升序排列后调用). ...

  5. Java文件编码自动转换工具类(只改变编码,不会改变文件内容)

    本篇随笔主要介绍了一个用java语言写的将一个文件编码转换为另一个编码并不改变文件内容的工具类: 通过读取源文件内容,用URLEncoding重新编码解码的方式实现. public class Cha ...

  6. Java操作XML的工具类

    import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.Inp ...

  7. java里poi操作excel的工具类(兼容各版本)

    转: java里poi操作excel的工具类(兼容各版本) 下面是文件内具体内容,文件下载: import java.io.FileNotFoundException; import java.io. ...

  8. java中常用的工具类(二)

    下面继续分享java中常用的一些工具类,希望给大家带来帮助! 1.FtpUtil           Java   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...

  9. JAVA中封装JSONUtils工具类及使用

    在JAVA中用json-lib-2.3-jdk15.jar包中提供了JSONObject和JSONArray基类,用于JSON的序列化和反序列化的操作.但是我们更习惯将其进一步封装,达到更好的重用. ...

随机推荐

  1. 如何编写Spring-Boot自动配置

    摘要 本文主要介绍如何把一个spring的项目(特别是一些公共工具类项目),基于spring boot的自动配置的思想封装起来,使其他Spring-Boot项目引入后能够进行快速配置. AutoCon ...

  2. 并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的Ha ...

  3. XCode 8.3 Automatically manage signing 问题

    记录iOS证书问题 无意中选择了Personal team,后台自动生成bundle identifier:com.xxx.project,导致不能在Organization team创建,而且在de ...

  4. 推荐xamlspy

    xamlspy(http://xamlspy.com/) 如果在win32时代用过spy++的,都应该在silverlight/wpf时代用一下xamlspy,让你重新找到用spy++看别人程序的UI ...

  5. python3实现TCP协议的简单服务器和客户端

    利用python3来实现TCP协议,和UDP类似.UDP应用于及时通信,而TCP协议用来传送文件.命令等操作,因为这些数据不允许丢失,否则会造成文件错误或命令混乱.下面代码就是模拟客户端通过命令行操作 ...

  6. 上下文Context详细介绍

    1.先看看它的继承结构,下图可以看出Context首先是一个抽象类,继承了Object,Activity,Service,Application都继承了它 2.API中对它的描述: @1Context ...

  7. 利用浏览器查找font-family的css编码

    提供一种利用Chrome快速查找字体编码的小技巧 打开浏览器,按下键盘F12 点击Console控制台 输入escape("要查询的字体中文名称")(注意:括号与引号都是英文输入法 ...

  8. Mac下终端配置(item2 + oh-my-zsh + solarized配色方案)

    最近重装了系统,于是便重新配置了一下终端,使其更符合用户习惯. 效果如下: 拥有语法高亮,命令行tab补全,自动提示符,显示Git仓库状态等功能. 安装 首先我们下载的 iTem2 这个软件,比Mac ...

  9. ssh无密码登录远程主机

    方法:在客户端生成公/私钥对,将私钥文件保存在客户端,再将公钥文件上传到服务器端(远程主机) 1.在客户端生成公/私钥对 cb@cb251#ssh-keygen...cb@cb251#ls .ssh/ ...

  10. [0] C# 扩展方法(Extension Method)

    有时有这样的情况,有一个类,你不能修改它,但你又想对它扩展(添加一个方法),这个时候就可以用到扩展方法了.请看下面的例子: using System;using System.Collections. ...