【Java】The Java Headless Mode

Xander發表於2023-02-28

原文

https://www.baeldung.com/java-headless-mode

引言

這篇文章源自個人看到了Kafka的啟動指令碼中一個“奇怪”的引數:

-Djava.awt.headless=true

拿去谷歌一下發現網上的描述都大差不差,這裡找了baeldung(類似國外的菜鳥教程)中的一篇文章,本文內容來自於英文部落格原文。

這篇文章介紹了 -Djava.awt.headless 引數的作用,網上大部分的資料都是說“為了提高計算效率和適配性我們可以使用這種模式,關閉圖形顯示等功能可以大大節省裝置的計算能力,而且對一些本身沒有相關顯示裝置的機器也能適配,程式也可以正常執行。”,個人認為這些理論內容不太能理解。

當然也有諸如伺服器沒有螢幕什麼的,你得告訴程式一聲,你工作的地方沒有這些裝置這種說法 ,為此找了一篇國外的部落格介紹。

如何設定?

設定方式如下:

  • 在system property中設定 _java.awt.headless_ 為 _true_。

SpringBoot的原始碼中可以找到類似的程式碼:

private void configureHeadlessProperty() {
        System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));
    }
  • 啟動指令碼中進行設定-Djava.awt.headless=true:在Kafka的指令碼當中存在類似的啟動指令碼。
KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -XX:MaxInlineLevel=15 -Djava.awt.headless=true"

最後一個引數顯示它使用headless模式。

  • 在執行命令的時候動態新增-Djava.awt.headless=true,這種方式和指令碼設定啟動的方式類似。

Headless 繞過重量級元件

如果一個帶有GUI元件的程式碼在開和關Headless模式下執行分別會有什麼不同的效果?

@Test  
public void FlexibleApp() {  
    if (GraphicsEnvironment.isHeadless()) {  
        System.out.println("Hello World");  
    } else {  
        JOptionPane.showMessageDialog(null, " showMessageDialog Hello World");  
    }  
}

上面的程式碼如果關閉了Headless模式,則列印Hello World會變為圖形化介面。

如果開啟Headless,則會列印在控制檯。

Headless Mode 在UI元件的應用案例

Java Headless Mode 的典型案例可能是使用圖形轉化器,我們有時候可能需要圖形資料進行影像處理,但是不一定需要實際顯示。

下面透過一個單元測試來模擬這些情況:

  @Before  
    public void setUpHeadlessMode() {  
        // 透過註釋掉下面的程式碼測試不同的效果  
//        System.setProperty("java.awt.headless", "true");  
    }  
  
    @Test  
    public void whenSetUpSuccessful_thenHeadlessIsTrue() {  
        boolean headless = GraphicsEnvironment.isHeadless();  
        Assert.assertTrue(headless);  
    }/*  
    測試透過  
    註釋下面的程式碼之後,單元測試不透過  
    //        System.setProperty("java.awt.headless", "true");    */

使用awt的元件java.awt.GraphicsEnvironment#isHeadless,注意較高版本的JDK(例如 JDK11)中awk被直接幹掉了,需要下載外部依賴匯入才可以使用,建議選擇JDK8以及以下的版本測試上面的程式。

上面的程式碼如果註釋掉 headless模式,單元測試會直接不透過。下面簡單構建了一個圖形轉化器:

@Test  
public void whenHeadlessMode_thenImagesWork() {  
    boolean result = false;  
    try (InputStream inStream = HeadlessModeUnitTest.class.getResourceAsStream(IN_FILE);  
         FileOutputStream outStream = new FileOutputStream(OUT_FILE)) {  
        BufferedImage inputImage = ImageIO.read(inStream);  
        result = ImageIO.write(inputImage, FORMAT, outStream);  
    }  
  
    assertThat(result).isTrue();  
}

在接下來的這個例子中,我們可以看到所有字型的資訊,包括字型的度量,也可以讓我們使用。

  @Test  
    public void whenHeadless_thenFontsWork() {  
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();  
        String fonts[] = ge.getAvailableFontFamilyNames();  
  
//        assertThat(fonts).isNotEmpty();  
  
        Font font = new Font(fonts[0], Font.BOLD, 14);  
        FontMetrics fm = (new Canvas()).getFontMetrics(font);  
  
//        assertThat(fm.getHeight()).isGreaterThan(0);  
//        assertThat(fm.getAscent()).isGreaterThan(0);  
//        assertThat(fm.getDescent()).isGreaterThan(0);  
    }

HeadlessException

有些裝置是需要外部裝置支援的,否則會丟擲下面的異常:

Exception in thread "main" java.awt.HeadlessException
    at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)
    at java.awt.Window.<init>(Window.java:536)
    at java.awt.Frame.<init>(Frame.java:420)

可以使用Frame來進行驗證:

  
   @Test  
   public void whenHeadlessmode_thenFrameThrowsHeadlessException() {  
       Frame frame = new Frame();  
       frame.setVisible(true);  
       frame.setSize(120, 120);  
   }/*  
   在開關Headless模式後會有不同的結果  
   開啟:透過      
   
   關閉  
   ava.awt.HeadlessExceptionat java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)  
at java.awt.Window.<init>(Window.java:536)  
at java.awt.Frame.<init>(Frame.java:420)  
at java.awt.Frame.<init>(Frame.java:385)  
  
   */

作為一個經驗法則,請記住,像Frame和Button這樣的頂級元件總是需要一個互動式的環境,並且會丟擲這個異常。然而,如果沒有明確設定無頭模式,它將被丟擲,成為一個不可恢復的錯誤。

總結

透過程式碼和案例分析,我們大致瞭解Java Headless Mode模式是怎麼一回事,說白了就是遮蔽掉外接裝置比如GUI的額外開銷,轉而用程式自己去進行模擬。

Kafka設定這樣的引數就是把效能發揮到機制,擯棄一切外部裝置干擾,讓伺服器儘可能的透過自身程式模擬外部裝置。

比如重量級元件控制檯列印,在外部設計可以透過JOptionPane的GUI元件實現視覺化效果,而Headless則是利用我們熟知的System.out控制檯輸入輸出流完成列印功能的模擬。

以上就是關於 Java Headless Mode 的理解。

程式demo

本文的個人實驗程式碼放到下面部分,文章提到的部分程式碼可能會無法編譯透過(圖形轉化器的程式碼),個人理解程式碼意圖之後就沒有深究了,讀者碰到報錯問題忽略刪除即可。

PS:建議使用JDK8之前的版本,可以直接引入awt和swing的相關元件。
  
import jdk.nashorn.internal.ir.LiteralNode;  
import org.junit.Assert;  
import org.junit.Before;  
import org.junit.Test;  
  
import javax.imageio.ImageIO;  
import javax.swing.*;  
import java.awt.*;  
import java.awt.image.BufferedImage;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
import static org.junit.Assert.assertThat;  
  
  
public class HandlessTest {  
  
    @Before  
    public void setUpHeadlessMode() {  
        // 透過註釋掉下面的程式碼測試不同的效果  
        System.setProperty("java.awt.headless", "true");  
    }  
  
    @Test  
    public void whenSetUpSuccessful_thenHeadlessIsTrue() {  
        boolean headless = GraphicsEnvironment.isHeadless();  
        Assert.assertTrue(headless);  
    }/*  
    測試透過  
    註釋下面的程式碼之後,單元測試不透過  
    //        System.setProperty("java.awt.headless", "true");    */  
  
    //@Test  
    //public void whenHeadlessMode_thenImagesWork() throws IOException {  
  
    //    boolean result = false;  
    //    try (InputStream inStream = HandlessTest.class.getResourceAsStream(IN_FILE);  
    //         FileOutputStream outStream = new FileOutputStream(OUT_FILE)) {  
    //        BufferedImage inputImage = ImageIO.read(inStream);  
    //        result = ImageIO.write(inputImage, FORMAT, outStream);  
    //   }  
    //    Assert.assertTrue(result);  
    //}  
  
//    @Test  
//    public void whenHeadlessMode_thenImagesWork() {  
//        boolean result = false;  
//        try (InputStream inStream = HeadlessModeUnitTest.class.getResourceAsStream(IN_FILE);  
//             FileOutputStream outStream = new FileOutputStream(OUT_FILE)) {  
//            BufferedImage inputImage = ImageIO.read(inStream);  
//            result = ImageIO.write(inputImage, FORMAT, outStream);  
//        }  
//  
//        assertThat(result).isTrue();  
//    }  
  
    @Test  
    public void whenHeadless_thenFontsWork() {  
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();  
        String fonts[] = ge.getAvailableFontFamilyNames();  
  
//        assertThat(fonts).isNotEmpty();  
  
        Font font = new Font(fonts[0], Font.BOLD, 14);  
        FontMetrics fm = (new Canvas()).getFontMetrics(font);  
  
//        assertThat(fm.getHeight()).isGreaterThan(0);  
//        assertThat(fm.getAscent()).isGreaterThan(0);  
//        assertThat(fm.getDescent()).isGreaterThan(0);  
    }  
  
    @Test  
    public void whenHeadlessmode_thenFrameThrowsHeadlessException() {  
        Frame frame = new Frame();  
        frame.setVisible(true);  
        frame.setSize(120, 120);  
    }/*  
    在開關Headless模式後會有不同的結果  
    開啟:透過  
    關閉  
    ava.awt.HeadlessException   at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:204)   at java.awt.Window.<init>(Window.java:536)   at java.awt.Frame.<init>(Frame.java:420)   at java.awt.Frame.<init>(Frame.java:385)  
    */  
  
  
    @Test  
    public void FlexibleApp() {  
        if (GraphicsEnvironment.isHeadless()) {  
            System.out.println("Hello World");  
        } else {  
            JOptionPane.showMessageDialog(null, "showMessageDialog Hello World");  
        }  
    }  
  
  
  
}

參考資料

https://www.jianshu.com/p/7248b3ff5ca7

https://www.baeldung.com/java-headless-mode](https://www.baeldung.com/java...](https://www.baeldung.com/java...))

相關文章