温斯顿吴的个人博客 woojean.com

命令行方式编译打包Java项目的各种形式总结

2017-05-17

下载代码:https://github.com/woojean/demos/tree/master/java-cmd-compile

不使用Maven、Gradle等构件工具,直接使用命令行来构建Java项目,加深对Java编译打包过程的理解。

编译执行单个不带包名的Java文件

如果没有指定包名,所有的示例都属于一个默认的无名包。

使用默认编译行为

javademo/src/First.java

public class First{
	public static void main(String args[]){
		System.out.println("First");
	}
}

注意:package声明不是必须的。

编译:

javac src/First.java  // 将在src目录下(源代码相同目录下)生产字节码文件First.class

运行:

cd src
java First  // 之所以这样可以运行,是因为在CLASSPAHT中配置了.,这样java在执行时会在当前目录下寻找字节码文件

输出:

First

在运行时添加CLASSPATH

如果这样执行:

java src/First

将会报错:

错误: 找不到或无法加载主类 src.First

当然也可以在执行Java时临时指定CLASSPATH:

java -cp src/ First  // 将src目录临时添加到CLASSPATH中

输出:

First

编译执行单个带包名的Java文件

javademo/src/First.java

package com.javademo;

public class First{

	public static void main(String args[]){
		System.out.println("First");
	}
}

编译:

javac src/First.java 

无法执行:

java -cp src/ First  // 错误: 找不到或无法加载主类 First
java -cp src/ com.javademo.First // 错误: 找不到或无法加载主类 First

cd src
java First // 错误: 找不到或无法加载主类 First
java com.javademo.First // 错误: 找不到或无法加载主类 First

Java包名必须是文件所在实际物理路径(将文件分隔符替换为.后)的一部分。 而为了保证编译后的Java字节码能够被执行(能够被Java虚拟机找到),该物理路径的前缀部分必须添加到CLASSPATH中(可以写配置文件,也可以在运行时指定,IDE就是在运行时添加)。

因此修改目录结构为:

src/com/javademo/First.java

编译:

javac src/com/javademo/First.java

执行:

java com.javademo.First  // OK!
java com/javademo/First  // OK!

注意如下执行是错误的:

java src/com/javademo/First  // 错误: 找不到或无法加载主类 src.com.javademo.First,因为src并不属于包名的一部分
java -cp src/com/javademo com.javademo.First  // 错误: 找不到或无法加载主类 com.javademo.First

即,使用java执行带包名的类时,会在当前路径下,以包名结构为路径结构来寻找待执行的字节码文件。 因此在src目录下执行如下命令:

java com.javademo.First  // OK!

因为当前目录(.)被添加到系统的CLASSPATH中,所以可以在当前目录(/Users/wujian/projects/javademo/src)下寻找字节码文件。 又因为com.javademo.First指定了包名,对应解析出来的类字节码位置为com/javademo/First,所以最终定位字节码文件的路径为: /Users/wujian/projects/javademo/src/com/javademo/First

因此,在src父目录执行:

java src/com/javademo/First

实际拼凑的字节码定位路径为:/Users/wujian/projects/javademo/src/com/javademo/First,路劲虽然是正确的,但是指定的类(src.com.javademo.First)却是不存在的。

而如下的执行:

java -cp src/com/javademo com.javademo.First

根据该指定CLASSPATH实际拼凑的字节码定位路径为:/Users/wujian/projects/javademo/src/com/javademo/com/javademo/First,显然错误。

总结一下寻找字节码的行为就是: 对于要运行的带包名的类,先按包名转换得到一段相对路径,再结合所有的CLASSPATH,拼凑CLASSPATH和包名相对路径得到要查找类的定位位置,然后再在该位置处查找与所需带包名的类(完全限定名)匹配的字节码。

寻找字节码时依赖字节码文件的名称

mv com/javademo/First.class com/javademo/Second.class
java com.javademo.First  // 错误: 找不到或无法加载主类 com.javademo.First

指定编译生成字节码文件的位置

编译

javac src/com/javademo/First.java -d target/

执行

cd target/
java com.javademo.First   // OK!

编译执行多个Java文件

javademo/src/First.java

package com.javademo;

public class First{

	public String output(){
		return "First";
	}

	public static void main(String args[]){

		System.out.println("First");
	}
}

javademo/src/Second.java

package com.javademo;

public class Second {

    public static String output() {
        First first = new First();
        String firstOutput = first.output();
        return firstOutput + "->" + "Second";
    }

    public static void main(String args[]) {

        System.out.println(Second.output());
    }
}

编译

javac src/com/javademo/First.java src/com/javademo/Second.java -d target/

执行

cd target/
java com.javademo.Second   // First->Second

如果分别单独编译将报错:

javac src/com/javademo/First.java -d target/    // OK!
javac src/com/javademo/Second.java -d target/ 

编译Second.java时报错:

src/com/javademo/Second.java:6: 错误: 找不到符号
        First first = new First();
        ^
  符号:   类 First
  位置: 类 Second

不过,一起编译时,文件列出的顺序并不依赖于类之间的引用关系:

javac src/com/javademo/Second.java src/com/javademo/First.java -d target/    // Second写在First前面 OK!

打包无MANIFEST的jar文件

将First类打包为一个jar文件。

cd target/
jar cvf First.jar com/javademo/First.class

输出:

已添加清单
正在添加: com/javademo/First.class(输入 = 495) (输出 = 314)(压缩了 36%)

执行:

java -cp First.jar com.javademo.First   // OK! 注意-cp选项

MANIFEST.MF的内容

Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)

打包多个文件并指定MANIFEST

vi mymanifest

Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: com.javademo.Second

打包:

jar cvfm Second.jar mymanifest com/javademo/First.class com/javademo/Second.class

执行:

java -jar Second.jar  // OK! 无需指定要执行的类

编译引用了第三方jar文件的项目

创建第三方jar文件

src/com/thirdparty/Third.java

package com.thirdparty;

public class Third {

    public String output() {
        return "Third";
    }

    public static void main(String args[]) {

        System.out.println("Third");
    }
}
javac src/com/thirdparty/Third.java -d target/
cd target/
jar cvf Third.jar com/thirdparty/Third.class

在项目中引用第三方jar包

jar包位置:

lib/Third.jar

代码中的引用:

package com.javademo;

import com.thirdparty.Third;

public class Fourth {

    public static String output() {
        Third third = new Third();
        String thirdOutput = third.output();
        return thirdOutput + "->" + "Fourth";
    }

    public static void main(String args[]) {

        System.out.println(Fourth.output());
    }
}

编译

javac -cp lib/Third.jar -d target/ src/com/javademo/Fourth.java 

运行

java com.javademo.Fourth  // Third->Fourth

打包引用了第三方jar文件的项目为jar文件

创建lib目录,并将要引用的jar文件放到lib目录中:

mkdir lib
cp Third.jar lib/Third.jar

vi mymanifest

Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: com.javademo.Fourth
Class-Path: lib/Third.jar

Class-Path可以有多个,用空格分割:

Class-Path: lib/commons-codec.jar lib/commons-httpclient-3.1.jar lib/commons-logging-1.1.jar

java执行程序只会到目录下寻找.class文件,而不会解压目录下的jar文件然后再在解压后的内容中寻找.class文件。所以,对于jar包的引用要指定路径。 所以这里Class-Path的行为和CLASSPATH的行为是不一样的。

打包:

cd target/
jar cvfm Fourth.jar mymanifest com/javademo/Fourth.class

运行:

java -jar Fourth.jar

输出:

Exception in thread "main" java.lang.NoClassDefFoundError: com/thirdparty/Third
	at com.javademo.Fourth.output(Fourth.java:8)
	at com.javademo.Fourth.main(Fourth.java:15)
Caused by: java.lang.ClassNotFoundException: com.thirdparty.Third
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 2 more

原因在于在当前执行路径下没有lib/First.jar.

cd target/
mkdir lib
cp Third.jar lib/Third.jar

运行:

java -jar Fourth.jar -cp lib/   // Third->Fourth

文章目录