打包jar方法

运行jar包解决方案
2006年08月18日 星期五 下午 07:01

当用java -jar yourJarExe.jar来运行一个经过打包的应用程序的时候,你会发现如何设置-classpath参数应用程序都找不到相应的第三方类,报 ClassNotFound错误。实际上这是由于当使用-jar参数运行的时候,java VM会屏蔽所有的外部classpath,而只以本身yourJarExe.jar的内部class作为类的寻找范围。

**解决方案**

一 BootStrap class扩展方案

Java 命令行提供了如何扩展bootStrap 级别class的简单方法.
-Xbootclasspath:     完全取代基本核心的Java class 搜索路径.
                                   不常用,否则要重新写所有Java 核心class
-Xbootclasspath/a: 后缀在核心class搜索路径后面.常用!!
-Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免
                                   引起不必要的冲突.

语法如下:
 java -Xbootclasspath/a:/usrhome/thirdlib.jar; -jar yourJarExe.jar
java ?Xbootclasspath:/d:/myclass/account.jar; -jar yourself.jar

非标准参数又称为扩展参数,其列表如下:
-Xint
 设置jvm以解释模式运行,所有的字节码将被直接执行,而不会编译成本地码。
 
-Xbatch
 关闭后台代码编译,强制在前台编译,编译完成之后才能进行代码执行;
 默认情况下,jvm在后台进行编译,若没有编译完成,则前台运行代码时以解释模式运行。
 
-Xbootclasspath:bootclasspath
 让 jvm从指定路径(可以是分号分隔的目录、jar、或者zip)中加载bootclass,用来替换jdk的rt.jar;若非必要,一般不会用到;
-Xbootclasspath/a:path
 将指定路径的所有文件追加到默认bootstrap路径中;
-Xbootclasspath/p:path
 让 jvm优先于bootstrap默认路径加载指定路径的所有文件;
 
-Xcheck:jni
 对 JNI函数进行附加check;此时jvm将校验传递给JNI函数参数的合法性,在本地代码中遇到非法数据时,jmv将报一个致命错误而终止;使用该参数后将造成性能下降,请慎用。
 
-Xfuture
 让jvm对类文件执行严格的格式检查(默认 jvm不进行严格格式检查),以符合类文件格式规范,推荐开发人员使用该参数。
 
-Xnoclassgc
 关闭针对class的gc功能;因为其阻止内存回收,所以可能会导致OutOfMemoryError错误,慎用;
 
-Xincgc
 开启增量gc(默认为关闭);这有助于减少长时间GC时应用程序出现的停顿;但由于可能和应用程序并发执行,所以会降低CPU对应用的处理能力。
 
-Xloggc:file
 与 -verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。
 若与 verbose命令同时出现在命令行中,则以-Xloggc为准。
 
-Xmsn
 指定jvm 堆的初始大小,默认为物理内存的1/64,最小为1M;可以指定单位,比如k、m,若不指定,则默认为字节。
 
-Xmxn
 指定jvm堆的最大值,默认为物理内存的1/4或者1G,最小为2M;单位与-Xms一致。
 
-Xprof
 跟踪正运行的程序,并将跟踪数据在标准输出输出;适合于开发环境调试。
 
-Xrs
 减少jvm 对操作系统信号(signals)的使用,该参数从1.3.1开始有效;
 从jdk1.3.0开始,jvm允许程序在关闭之前还可以执行一些代码(比如关闭数据库的连接池),即使jvm被突然终止;
 jvm关闭工具通过监控控制台的相关事件而满足以上的功能;更确切的说,通知在关闭工具执行之前,先注册控制台的控制handler,然后对CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT这几类事件直接返回true。
 但如果jvm以服务的形式在后台运行(比如servlet引擎),他能接收CTRL_LOGOFF_EVENT事件,但此时并不需要初始化关闭程序;为了避免类似冲突的再次出现,从jdk1.3.1开始提供-Xrs参数;当此参数被设置之后,jvm将不接收控制台的控制handler,也就是说他不监控和处理 CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT事件。
 
-Xssn
 设置单个线程栈的大小,一般默认为512k。

上面这些参数中,比如-Xmsn、-Xmxn……都是我们性能优化中很重要的参数;
-Xprof、 -Xloggc:file等都是在没有专业跟踪工具情况下排错的好手;
在上一小节中提到的关于JProfiler的配置中就使用到了-Xbootclasspath/a:path;


方法一、使用Bootstrap Classloader来加载这些类。

 

我们可以在运行时使用如下参数:

 

-Xbootclasspath:完全取代系统 Java classpath.最好不用。
-Xbootclasspath/a: 在系统class加载后加载。一般用这个。
-Xbootclasspath/p: 在系统class加载前加载,注意使用,和系统类冲突就不好了.
win32     java -Xbootclasspath/a: some.jar;some2.jar;  -jar test.jar
unix          java -Xbootclasspath/a: some.jar:some2.jar:  -jar test.jar
win32系统每个jar用分号隔开,unix系统下用冒号隔开

 

 

 

方法二、使用Extension Classloader来加载

 

你可以把需要加载的jar都扔到%JRE_HOME%/lib/ext下面,这个目录下的jar包会在Bootstrap Classloader工作完后由Extension Classloader来加载。非常方便,非常省心。:)

 

 

 

方法三、还是用AppClassloader来加载,不过不需要classpath参数了

 

我们在MANIFEST.MF中添加如下代码:

Class-Path: lib/some.jar

 

lib是和test.jar同目录的一个子目录,test.jar要引用的some.jar包就在这里面。

然后测试运行,一切正常!

 

如果有多个jar包需要引用的情况:

Class-Path: lib/some.jar lib/some2.jar

每个单独的jar用空格隔开就可以了。注意使用相对路径。

 

另:如果META-INF 下包含INDEX.LIST文件的话,可能会使Class-Path配置失效。INDEX.LIST是Jar打包工具打包时生成的索引文件,删除对运行不产生影响。

 

 

方法四、自定义Classloader来加载

这种方法是终极解决方案,基本上那些知名java应用都是那么干的,如tomcat、jboss等等。

这种方式有点复杂,需要专门开贴讨论。关于ClassLoader的原理和自定义 ClassLoader可以参考这篇  http://longdick.javaeye.com/blog /442213。

 

 

总结:

以上四种方法都可以用,特别是程序运行在非常单纯的环境中时。但是,如果是运行在多任务,多应用的环境中时,最好每个应用都能相互独立,第一种和第二种方案都有可能对其他应用产生影响,因此最好就是选择第三种和第四种。

 


  如何方便的指定Java程序运行所需要的所有jar包 收藏
学Java的人经常遇到的一个问题是:如果一个程序依赖某个文件夹下的一堆jar包,那么启动它的时候就需要在java -cp参数后面一个一个的加上jar包的名称,很不方便。

        比如主程序类叫Main,在目录lib下有aaa.jar,bbb.jar,ccc.jar,则需要输入以下命令才能执行:java -cp lib/aaa.jar;lib/bbb.jar;/lib.ccc.jar  Main

        (linux系统下用冒号,windows下用分号)。

        如果jar包少,倒也不是很麻烦,但如果依赖的jar包数量很多的话,一个个的输就比较麻烦了,当然我们也可以借助一些脚本或者Ant来实现自动化,但总觉得杀鸡焉用牛刀,反而把事情弄麻烦了。

我自己是这样解决的:

java -Djava.ext.dirs=./lib -cp ./bin Main    (假设主程序类Mian.class放在bin目录下)

       正如你说看到的,-Djava.ext.dirs起到了关键作用,它将告诉JVM从那里加载一些类,为了方便理解记忆,顺便补充一点 ClassLoader的常识:

       Java的类装载模型是一种代理(delegation)模型。当JVM 要求类装载器CL(ClassLoader)装载一个类时,CL首先将这个类装载请求转发给他的父装载器。只有当父装载器没有装载并无法装载这个类时, CL才获得装载这个类的机会。这样, 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器(bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装载器时, 如果没有显式地给出父装载器, 那么JVM将默认系统装载器为其父装载器。

       根(Bootstrap) 装载器:该装载器没有父装载器,它是JVM实现的一部分,从sun.boot.class.path装载运行时库的核心代码。
      

       扩展(Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯Java代码实现的,它从java.ext.dirs (扩展目录)中装载代码。(这一段就是为什么可以通过设置-Djava.ext.dirs来加载一堆jar的原理)




======================================
 通过ClassLoader调用外部jar包 收藏

我们大家都知道,每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类。

系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类,对于非默认的jar,一般只能手动在配置环境添加。

但事实上,我们可以通过Thread.currentThread().setContextClassLoader()更改当前线程的contextClassLoader行为,实现在程序内加载外部jar。

PS:
ClassLoader的工作原理是:
1) 线程需要用到某个类时,contextClassLoader将被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入


package org.loon.framework.jar;

/** *//**
 *

Title: LoonFramework


 *

Description:JarLoader,用于jar包的外部操作


 *

Copyright: Copyright (c) 2007


 *

Company: LoonFramework


 * @author chenpeng 
 * @email:[email protected]
 * @version 0.1
 */
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

public class JarLoader extends ClassLoader ...{
    //资源缓存
    public static Hashtable resources = new Hashtable();
 
    public static JarLoader loader = new JarLoader();

    public static Class load(byte[] resource) throws Exception ...{
        // 主函数所在类全称
        String mainClassName = "";
        //class资源及实体缓存
        ArrayList classNames = new ArrayList();
        ArrayList classBuffers = new ArrayList();
        // 存储依赖类
        HashMap depends = new HashMap();
        // 将byte[]转为JarInputStream
        JarInputStream jar = new JarInputStream(new ByteArrayInputStream(
                resource));
        Manifest manifest = jar.getManifest();
        // 当Main-Class被声明时,获得主函数所在类全称
        if (manifest != null) ...{
            mainClassName = manifest.getMainAttributes().getValue("Main-Class");
        }
        // 依次获得对应JAR文件中封装的各个被压缩文件的JarEntry
        JarEntry entry;
        while ((entry = jar.getNextJarEntry()) != null) ...{
            // 当找到的entry为class时
            if (entry.getName().toLowerCase().endsWith(".class")) ...{
                // 将类路径转变为类全称
                String name = entry.getName().substring(0,
                        entry.getName().length() - ".class".length()).replace(
                        '/', '.');
                // 加载该类
                byte[] data = getResourceData(jar);
                // 缓存类名及数据
                classNames.add(name);
                classBuffers.add(data);

            } else ...{
                // 非class结尾但开头字符为'/'时
                if (entry.getName().charAt(0) == '/') ...{
                    resources.put(entry.getName(), getResourceData(jar));
                // 否则追加'/'后缓存   
                } else ...{
                    resources.put("/" + entry.getName(), getResourceData(jar));
                }
            }
        }
        //当获得的main-class名不为空时
        while (classNames.size() > 0) ...{
            //获得类路径全长
            int n = classNames.size();
            for (int i = classNames.size() - 1; i >= 0; i--) ...{
                try ...{
                    //查询指定类
                    loader.defineClass((String) classNames.get(i),
                            (byte[]) classBuffers.get(i), 0,
                            ((byte[]) classBuffers.get(i)).length);
                    //获得类名
                    String pkName = (String) classNames.get(i);
                    if (pkName.lastIndexOf('.') >= 0) ...{
                        pkName = pkName
                                .substring(0, pkName.lastIndexOf('.'));
                        if (loader.getPackage(pkName) == null) ...{
                            loader.definePackage(pkName, null, null, null,
                                    null, null, null, null);
                        }
                    }
                    //查询后删除缓冲
                    classNames.remove(i);
                    classBuffers.remove(i);
                } catch (NoClassDefFoundError e) ...{
                    depends.put((String) classNames.get(i), e.getMessage()
                            .replaceAll("/", "."));
                } catch (UnsupportedClassVersionError e) ...{
                    //jre版本错误提示
                    throw new UnsupportedClassVersionError(classNames.get(i)
                            + ", " + System.getProperty("java.vm.name") + " "
                            + System.getProperty("java.vm.version") + ")");
                }
            }
            if (n == classNames.size()) ...{
                for (int i = 0; i < classNames.size(); i++) ...{
                    System.err.println("NoClassDefFoundError:"
                            + classNames.get(i));
                    String className = (String) classNames.get(i);
                    while (depends.containsKey(className)) ...{
                        className = (String) depends.get(className);
                    }
                }
                break;
            }
        }
        try ...{
            //加载
            Thread.currentThread().setContextClassLoader(loader);
            // 获得指定类,查找其他类方式相仿
            return Class.forName(mainClassName, true, loader);
        } catch (ClassNotFoundException e) ...{
            String className = mainClassName;
            while (depends.containsKey(className)) ...{
                className = (String) depends.get(className);
            }
            throw new ClassNotFoundException(className);
        }
    }

    /** *//**
     * 获得指定路径文件的byte[]形式
     * @param name
     * @return
     */
    final static public byte[] getDataSource(String name) ...{
        FileInputStream fileInput;
        try ...{
            fileInput = new FileInputStream(new File(name));
        } catch (FileNotFoundException e) ...{
            fileInput = null;
        }
        BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
        return getDataSource(bufferedInput);
    }
   
    /** *//**
     * 获得指定InputStream的byte[]形式
     * @param name
     * @return
     */
    final static public byte[] getDataSource(InputStream is) ...{
        if (is == null) ...{
            return null;
        }
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] arrayByte = null;
        try ...{
            byte[] bytes = new byte[8192];
            bytes = new byte[is.available()];
            int read;
            while ((read = is.read(bytes)) >= 0) ...{
                byteArrayOutputStream.write(bytes, 0, read);
            }
            arrayByte = byteArrayOutputStream.toByteArray();
        } catch (IOException e) ...{
            return null;
        } finally ...{
            try ...{
                if (byteArrayOutputStream != null) ...{
                    byteArrayOutputStream.close();
                    byteArrayOutputStream = null;
                }
                if (is != null) ...{
                    is.close();
                    is = null;
                }

            } catch (IOException e) ...{
            }
        }
        return arrayByte;
    }

    /** *//**
     * 获得指定JarInputStream的byte[]形式
     * @param jar
     * @return
     * @throws IOException
     */
     final static private byte[] getResourceData(JarInputStream jar)
            throws IOException ...{
        ByteArrayOutputStream data = new ByteArrayOutputStream();
        byte[] buffer = new byte[8192];
        int size;
        while (jar.available() > 0) ...{
            size = jar.read(buffer);
            if (size > 0) ...{
                data.write(buffer, 0, size);
            }
        }
        return data.toByteArray();
    }

     /** *//**
      * 重载的getResource,检查是否重复包含
      */
    public URL getResource(String name) ...{
        if (resources.containsKey("/" + name)) ...{
            try ...{
                return new URL("file:///" + name);
            } catch (MalformedURLException e) ...{
                e.printStackTrace();
            }
        }
        return super.getResource(name);
    }

    /** *//**
     * 执行指定class类
     * @param clz
     * @param methodName
     * @param args
     */
    public static void callVoidMethod(Class clz, String methodName,
            String[] args) ...{
        Class[] arg = new Class[1];
        arg[0] = args.getClass();
        try ...{
            Method method = clz.getMethod(methodName, arg);
            Object[] inArg = new Object[1];
            inArg[0] = args;
            method.invoke(clz, inArg);
        } catch (Exception e) ...{
            System.err.println(e.getMessage());
        }
    }
   
     /** *//**
      * 重载的getResourceAsStream,检查是否重复包含
      */
    public InputStream getResourceAsStream(String name) ...{
        if (name.charAt(0) == '/') ...{
            name = name.substring(1);
        }
        if (resources.containsKey("/" + name)) ...{
            return new ByteArrayInputStream((byte[]) resources.get("/" + name));
        }
        return super.getResourceAsStream(name);
    }

}


运行示例:

package org.loon.framework.jar;


/** *//**
 *


 * Title: LoonFramework
 *


 *


 * Description:从外部启动jar包
 *


 *


 * Copyright: Copyright (c) 2007
 *


 *


 * Company: LoonFramework
 *


 *
 * @author chenpeng
 * @email:[email protected]
 * @version 0.1
 */
public class JarTest ...{

    public static void main(String[] args) ...{
        //将jar包转为byte[]
        byte[] resource = JarLoader.getDataSource("D:/fps_test.jar");
        try ...{
            //通过byte[]获得主函数所在类
            Class clz = JarLoader.load(resource);
            //调用main函数
            JarLoader.callVoidMethod(clz, "main", new String[] ...{""});
        } catch (Exception e) ...{
            e.getStackTrace();
        }

    }

}

这时即使指定jar包没有被我们添加到lib中,程序依旧被顺利启动了。

但是有个缺点,在没有优化的前提下,这种直接加载外部包的速度在jvm会有很大损耗。

我们可以看到,fps值降低到正常值的50%左右(此fps实例见我CSDN以前的文章),所以并不适合直接运用在需要复杂运算的jar中类调用上(当然,有兴趣的话,可以在代码中优化,我自己在项目中写的另一个例子速度明显比这个快)。但是对于运算量小的外部jar调用,还是很方便的。

你可能感兴趣的:(打包jar方法)