寫作目標
記錄常見的使用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