译者注
原文:
https://www.baeldung.com/java-headless-mode
Demo
https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-2
1. 前言
有时,我们需要在没有真实的显示器、键盘、鼠标的情况下,来实现基于图形的Java应用程序,也就是说,程序运行在服务器或容器上。
本文我们将会学习Java的无头模式,以便实现上面提到的需求。我们将会知道,在无头模式的情况下,我们可以做什么、不能做什么。
2. 设置无头模式
在Java中,我们有很多方式来设置无头模式:
- 将系统属性
java.awt.headless
设置为true - 使用命令行参数
java -Djava.awt.headless=true
- 在服务的启动脚本中,添加参数
-Djava.awt.headless=true
到JAVA_OPTS
环境变量中
如果环境设置为无头模式,JVM就会识别到它。然而这样就会有一些细微的区别。我们来具体看一下。
3. 无头模式的UI组件示例
无头环境中的UI组件一个典型使用情况,就是作为图像转换器程序。
尽管它在运行过程中需要图形数据,但他并不需要显示。这样的app将会运行在服务器中,转换后的数据将会保存并通过网络传输给另一台机器来显示。
我们看一下如何操作。
首先,我们在JUnit
类中启用无头模式,
@Before
public void setUpHeadlessMode() {
System.setProperty("java.awt.headless", "true");
}
为了确保无头模式已经正常开启,我们可以写一个测试程序,通过调用java.awt.GraphicsEnvironment
来断言无头模式是true:
@Test
public void whenSetUpSuccessful_thenHeadlessIsTrue() {
assertThat(GraphicsEnvironment.isHeadless()).isTrue();
}
通过上面的测试方法,我们就可以准确的了解当前无头模式是否已经启用。
现在我们来做一个简单的图像转换器:
@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(removeAlphaChannel(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);
}
4. 无头异常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() {
assertThatExceptionOfType(HeadlessException.class).isThrownBy(() -> {
Frame frame = new Frame();
frame.setVisible(true);
frame.setSize(120, 120);
});
}
根据经验,需要注意的是,这些顶级组件(比如Frame或者Button)需要交互环境,否则就会抛出异常。如果无头模式没有开启,它们可能会取而代之的抛出非交互错误irrecoverable Error。
5. 在无头模式中绕过重量级组件
在本小节中,我们来提出一个问题:
如果我们把一个带有GUI组件的代码分别在“有头的生产环境机器上”和“无头的代码分析服务器上”运行,会发生什么?
在上面的例子中,我们已经知道了重量级组件无法在服务器上运行,并且会抛出异常。
所有我们可以使用一个条件来达到目的:
public void FlexibleApp() {
if (GraphicsEnvironment.isHeadless()) {
System.out.println("Hello World");
} else {
JOptionPane.showMessageDialog(null, "Hello World");
}
}
用这样的模式,我们可以创造一个灵活的程序,根据它所在的环境来自动调整行为。
6、 总结
通过不同的代码示例,我们了解了Java的无头模式和它的部分原理。在这篇文章中提供了兼容列表,列表中给出了无头模式中可以进行哪些操作。
和往常一样,你可以在Github上找到本文中的示例代码。