【JDK命令列 一】手動編譯Java原始碼與執行位元組碼命令合集(含外部依賴引用)

東北小狐狸發表於2021-05-27

寫作目標

記錄常見的使用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"));
    }
}

fastjsoncommons.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. 編譯有依賴關係的原始碼

兩種方法:

  1. 按順序編譯分別編譯(編譯被依賴,再編譯依賴)
  2. 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

相關文章