需求背景:写了一个实时读取日志文件以及监控的小程序,打包成了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包文件内容等操作,供大家参考。
packagecom.agent.util;importjava.io.BufferedReader;importjava.io.ByteArrayOutputStream;importjava.io.FileNotFoundException;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.Enumeration;importjava.util.Iterator;importjava.util.Map;importjava.util.Set;importjava.util.TreeMap;importjava.util.jar.JarEntry;importjava.util.jar.JarFile;importjava.util.jar.JarOutputStream;public classJarUtil {/*** 读取jar包所有的文件内容,显示JAR文件内容列表
*@paramjarFileName
*@throwsIOException*/
public static void readJARList(String jarFilePath) throwsIOException {//创建JAR文件对象
JarFile jarFile = newJarFile(jarFilePath);//枚举获得JAR文件内的实体,即相对路径
Enumeration en =jarFile.entries();
System.out.println("文件名\t文件大小\t压缩后的大小");//遍历显示JAR文件中的内容信息
while(en.hasMoreElements()) {//调用方法显示内容
process(en.nextElement());
}
}//显示对象信息
private static voidprocess(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包里面指定文件的内容
*@paramjarFileName jar包文件路径
*@paramfileName 文件名
*@throwsIOException*/
public static void readJarFile(String jarFilePath,String fileName) throwsIOException{
JarFile jarFile= newJarFile(jarFilePath);
JarEntry entry=jarFile.getJarEntry(fileName);
InputStream input=jarFile.getInputStream(entry);
readFile(input);
jarFile.close();
}public static void readFile(InputStream input) throwsIOException{
InputStreamReader in= newInputStreamReader(input);
BufferedReader reader= newBufferedReader(in);
String line ;while((line = reader.readLine())!=null){
System.out.println(line);
}
reader.close();
}/*** 读取流
*
*@paraminStream
*@return字节数组
*@throwsException*/
public static byte[] readStream(InputStream inStream) throwsException {
ByteArrayOutputStream outSteam= newByteArrayOutputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer,0, len);
}
outSteam.close();
inStream.close();returnoutSteam.toByteArray();
}/*** 修改Jar包里的文件或者添加文件
*@paramjarFile jar包路径
*@paramentryName 要写的文件名
*@paramdata 文件内容
*@throwsException*/
public static void writeJarFile(String jarFilePath,String entryName,byte[] data) throwsException{//1、首先将原Jar包里的所有内容读取到内存里,用TreeMap保存
JarFile jarFile = newJarFile(jarFilePath);//可以保持排列的顺序,所以用TreeMap 而不用HashMap
TreeMap tm = newTreeMap();
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(newFileOutputStream(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= newJarEntry(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 = newJarEntry(entryName);
jos.putNextEntry(newEntry);
jos.write(data,0, data.length);
}
jos.finish();
jos.close();
}/*** 测试案例
*@paramargs
*@throwsException*/
public static void main(String args[]) throwsException{// 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");
}
}
View Code
上面有个测试案例,测试读取其中一个配置文件,然后修改这个文件,最后再读取出来。 一个30M左右的Jar包文件耗时7秒多,实在忍受不了。网上找了很多文章,同时看了Zip相关的源码,也木有找到可以直接修改Jar包文件内容的方法。 最后只能另辟蹊径,找到了一个执行效率非常快的方法。
关于工具类没有删除文件的方法,大家根据修改的方法稍微改造一下即可。
工具类参考的博客:
如何快速修改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(newInputStreamReader(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还不够强大,希望后续可以完善一下