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

寫作目標

記錄常見的使用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參考://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

參考資料:

//docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html

//docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

//zhuanlan.zhihu.com/p/74229762

本文首發部落格園Hellxz部落格,//www.cnblogs.com/hellxz/p/14819191.html
同步於CSDN 拾級而上,//blog.csdn.net/u012586326/article/details/117335227