原文
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...))