寫作目標
記錄常見的使用javac手動編譯Java源碼和java手動執行字節碼的命令,一方面用於應對 Maven 和 Gradle 暫時無法使用的情況,臨時生成class文件(使用自己的jar包);另一方面了解下構建工具做了哪些工作。
作者水平有限,行文中如有錯誤,希望評論告知,自當盡快修復。
一、編譯源碼
1. javac 命令
編譯Java源碼都是使用 javac 命令完成的,其語法如下:
javac [ options ] [ sourcefiles ] [ classes] [ @argfiles ]
- options:選項參數,比如-cp,-d
- sourcefiles:java源文件,多個文件以空格分開
- classes:用來處理處理注解
- @argfiles,就是包含 option 或 java 文件列表的文件路徑,用@符號開頭,就像上面的@javaOptions.txt和@javaFiles.txt
2. 編譯僅使用 JDK 類庫源碼
javac sourcefiles
示例:
Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
編譯Main.java
javac Main.java
3. 指定文字編碼
默認將使用平台的字符集,如Windows是GBK。為了防止亂碼一般指定為 utf-8
javac -encoding encoding sourcefiles
示例:
#指定utf-8編碼編譯
javac -encoding utf-8 Main.java
4. 指定輸出字節碼路徑
class文件將輸出到指定路徑下,如果有package,也會一並在指定路徑下創建
javac -d path sourcefiles
示例:
#生成字節碼到classes目錄中
javac -d classes Main.java
注意:指定的目錄需要提前創建
5. 指定classpath
指定JVM查找用戶類文件、注解解釋器和源文件的目錄,即字節碼、源碼等的查找位置。
classpath確定流程:先從環境變量 CLASSPATH 中獲取,當用戶指定classpath時將覆蓋環境變量,如果沒有環境變量且未用戶設定,將以執行javac的路徑向下查找。
#有以下兩種寫法,二者等效
javac -cp path sourcefiles
javac -classpath path sourcefiles
#path可以使用通配符*來匹配目錄下一級jar包或class文件,比如下列寫法
#javac -cp "libs/*" sourcefiles
示例:
引用 FastJson 的Main.java
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
JSONObject json = new JSONObject();
json.put("hello", "world");
System.out.println(json.toJSONString());
}
}
編譯Main.java,fastjson的jar與Main.java同級目錄,直接寫jar包作為classpath僅適用於單個jar包引用時
javac -cp fastjson-1.2.73.jar Main.java
javac -classpath fastjson-1.2.73.jar Main.java
當設置需要設置多個目錄作為classpath時,在不同平台的寫法不大一樣
Linux/Unix平台
javac -cp "path1/*:path2/*" sourcefiles
javac -classpath "path1/*:path2/*" sourcefiles
Windows平台
javac -cp "path1\*;path2\*" sourcefiles
javac -classpath "path1\*;path2\*" sourcefiles
不同點僅在於多個目錄間使用 : 還是 ; 作為路徑分割符、目錄分割符是 / 還是 \
6. 指定外部目錄
指定外部目錄,javac 在編譯字節碼時將會從下列目錄中讀取字節碼或Jar包,完成編譯。
#有以下兩種寫法,二者等效
javac -extdirs directories sourcefiles
-Djava.ext.dirs=directories sourcefiles
示例:
編寫Main.java引用多個jar包,指定外部目錄編譯
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
public class Main {
public static void main(String[] args) {
JSONObject json = new JSONObject();
json.put("hello", "world");
System.out.println(json.toJSONString());
System.out.println(StringUtils.equals("1", "1"));
}
}
fastjson 與 commons.lang3 處於同目錄 libs 時,編譯命令:
javac -extdirs libs Main.java
javac -Djava.ext.dirs=libs Main.java
7. 帶package源碼編譯
Java使用目錄作為package定位字節碼,減少了重名問題,編譯方式是類似的,可使用通配符來匹配待編譯的 .java 文件
#src目錄下一級目錄查找java源碼文件編譯
javac src/*.java
#使用以上方式可能會少編譯一些深層次目錄下的源碼,推薦使用操作系統的命令來查找
#Linux平台
javac $(find src -name "*.java")
#Windows平台
where -r src *.java #收集源文件列表
javac <源文件列表> #手動拼接源文件路徑,多個源文件以空格分開,如 javac package/A.java package/B.java
此種方式編譯少量源文件還可以,源文件過長就會出現命令參數過長報錯,可以參考下面章節中的 使用參數文件簡化命令 解決此問題
8. 編譯有依賴關系的源碼
兩種方法:
- 按順序編譯分別編譯(編譯被依賴,再編譯依賴)
- 由
javac自動編譯(將要編譯的源文件列表全部給到javac命令后,順序無所謂)
示例:
PrintService.java
public class PrintService {
public void print(String msg){
System.out.println(msg);
}
}
Main.java
public class Main {
public static void main(String[] args) {
PrintService printService = new PrintService();
printService.print("Hello World!");
}
}
1.按順序編譯:
javac PrintService.java
javac Main.java
2.自動編譯
javac Main.java PrintService.java
9. 使用參數文件簡化命令
參數文件可以是javac命令中的部分內容,比如可以是java文件的路徑列表文件,將參數保存為文本中供編譯時引用,縮短執行命令長度,避免命令行參數過長報錯。
可匹配源碼目錄下的java文件列表作為參數文件
find src -name "*.java" > sourcefiles.txt
sourcefiles.txt
Main.java
PrintService.java

接着就可以通過 @+sourcefiles.txt 對列表文件進行引用,放到javac命令行中
#生成字節碼與源碼同目錄
javac @sourcefiles.txt
#指定存在的目錄輸出字節碼
javac -d target @sourcefiles.txt
當然不僅是源碼列表,還可以加上參數,如圖

10. 編譯腳本示例
10.1. Linux編譯腳本 compile.sh
源碼文件處在 src 目錄中,創建輸出目錄 target, 依賴包目錄 lib
在工程目錄下創建如下編譯腳本:
compile.sh
#!/bin/bash
# 編譯腳本
# @author: Hellxz
#出現變量取值失敗、報錯立即停止
set -eu
#定義變量
SOURCE=src
TARGET=target
LIBRARY=libs
#清理歷史編譯結果
[ -d "${TARGET}" ] && rm -rf ${TARGET}/*
#輸出參數文件
echo "-d ${TARGET} -encoding utf-8" > argsfile
find ${SOURCE} -name "*.java" >> argsfile
#編譯源文件
if [ -d "${LIBRARY}" ]; then
javac -cp "${LIBRARY}/*" @argsfile
else
javac @argsfile
fi
#刪除參數文件
rm -rf argsfile
echo "compile success!"
10.2. Windows編譯腳本 compile.bat
@echo off
REM 源碼目錄
set srcdir=src
REM 目標目錄
set targetdir=target
REM jar包外部依賴目錄
set libsdir=libs
REM 清理緩存
if exist %targetdir% (
echo clean target caches...
del /f /s /q %targetdir%\*.*
rd /s /q %targetdir%
md %targetdir%
echo clean caches success!
) else (
md %targetdir%
)
REM 生成參數文件
echo generating argsfile.txt
echo -d %targetdir% -encoding utf-8 > argsfile.txt
where -r %srcdir% *.java >> argsfile.txt
echo generate argsfile success!
REM 編譯
echo compiling...
if exist %libsdir% (
javac -cp "%libsdir%\*" @argsfile.txt
) else (
javac @argsfile.txt
)
REM 刪除參數文件
del /f /q /a argsfile.txt
echo compile success!
pause
二、執行字節碼
1. java 執行字節碼命令
java 命令用於執行 javac編譯出的字節碼文件,啟動 Java 虛擬機。
java 命令語法為:
java [options] classname [args]
- options:選項參數,包含Java虛擬機參數等設定
- classname:字節碼文件,
.class后綴的文件 - args:參數,將作為 main 方法的參數傳入程序中
options參考:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html#CBBIJCHG
2. 執行字節碼文件
一般而言,執行 Java 程序直接用 java 命令就可以
#執行帶main方法的字節碼
java mainclass
3. 執行帶package的字節碼
當源碼中有package提示包名時,執行的class需要放在層層包名目錄中,舉例:
package samples;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
編譯后Main.class 的位置在 /opt/target/samples/Main.class
執行java命令就需要進到 /opt/target 下,與第一層包目錄平級
cd /opt/target
java samples/Main.class
不進入包名目錄上級,可以設置 classpath 來指定待執行查找class的起點
java -cp /opt/target samples/Main
4. 執行有外部依賴關系的字節碼
src/samples/Main.java
package samples;
import com.alibaba.fastjson.JSONObject;
public class Main {
public static void main(String[] args) {
JSONObject json = new JSONObject();
json.put("hello", "world");
System.out.println(json.toJSONString());
}
}
外部依賴libs目錄、源碼目錄src、生成class目錄target,src下有一個包為samples的Main.java,如下圖

編譯src目錄源碼,生成字節碼到target下
javac -cp "src:libs/*" -d target $(find src -name "*.java")

設置classpath,執行字節碼文件
java -cp "target:libs/*" samples.Main

參考資料:
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
https://zhuanlan.zhihu.com/p/74229762
本文首發博客園Hellxz博客,https://www.cnblogs.com/hellxz/p/14819191.html
同步於CSDN 拾級而上,https://blog.csdn.net/u012586326/article/details/117335227
