You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot整合Swing部署外部Tomcat遇HeadlessException等Bean异常

解决Spring Boot+Swing打包War部署外部Tomcat的HeadlessException问题

你这踩了个Spring Boot整合Swing的典型坑——外部Tomcat默认以Headless模式运行,而你的Rest Controller同时继承了JFrame,Spring启动时会自动实例化这个Bean,直接触发图形环境缺失的异常,而且你之前在main方法里设置的builder.headless(false)在War部署场景下根本不会生效,因为这时候Spring是通过SpringBootServletInitializer启动的,main方法压根不会被执行。

下面是一步步的具体解决方案:

1. 分离Swing组件与Rest Controller(核心修复)

你的TLOG_Backup_Report同时是@RestController和JFrame,Spring容器启动时会自动实例化这个Bean,而外部Tomcat没有图形环境,直接就会抛出HeadlessException。正确的做法是把Swing的UI逻辑抽成独立类,不要让Spring管理它的实例,只在请求触发时才动态创建。

重构后的代码示例:

独立的Swing UI类

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Vector;

@SuppressWarnings("serial")
class SpringTable1 extends JTable {
    public SpringTable1(DefaultTableModel dm) {
        super(dm);
        setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
    }

    public SpringTable1() {
        setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
    }

    @Override
    public void doLayout() {
        int width = getWidth();
        int columnCount = getColumnCount();
        int columnSize = width / columnCount;
        for (int index = 0; index < columnCount; index++) {
            TableColumn column = getColumnModel().getColumn(index);
            column.setResizable(true);
            column.setPreferredWidth(width);
            column.setMinWidth(100);
        }
        super.doLayout();
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return true;
    }
}

// 独立的Swing窗口类,不要被Spring容器管理
class TlogReportFrame extends JFrame {
    public TlogReportFrame(DefaultTableModel tableModel) {
        SpringTable1 table = new SpringTable1(tableModel);
        table.setRowHeight(40);
        
        setTitle("TLOG Backup Report");
        setSize(1000, 1000);
        setResizable(true);
        setLocationRelativeTo(null);
        getContentPane().add(new JScrollPane(table));
        setVisible(true);
    }
}

修改后的Rest Controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.sql.*;
import java.util.Vector;
import javax.swing.table.DefaultTableModel;

@RestController
public class TLOG_Backup_Report {

    @RequestMapping("/tlog")
    public String getTlogStatusReport() throws ClassNotFoundException, SQLException {
        // 1. 先处理数据库查询逻辑
        Class.forName("你的数据库驱动类");
        String dbURL = "你的数据库连接URL";
        // 使用try-with-resources自动关闭资源
        try (Connection con = DriverManager.getConnection(dbURL);
             CallableStatement proc_stmt = con.prepareCall("你的存储过程调用语句");
             ResultSet rs = proc_stmt.executeQuery()) {

            DefaultTableModel tableModel = buildTableModel(rs);
            
            // 2. 动态关闭Headless模式,再创建Swing窗口
            System.setProperty("java.awt.headless", "false");
            new TlogReportFrame(tableModel);
            
            return "Results are displayed in the Swing window";
        }
    }

    public static DefaultTableModel buildTableModel(ResultSet rs) throws SQLException {
        ResultSetMetaData metaData = rs.getMetaData();
        Vector<String> columnNames = new Vector<>();
        int columnCount = metaData.getColumnCount();
        for (int column = 1; column <= columnCount; column++) {
            columnNames.add(metaData.getColumnName(column));
        }

        Vector<Vector<Object>> data = new Vector<>();
        while (rs.next()) {
            Vector<Object> vector = new Vector<>();
            for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
                vector.add(rs.getObject(columnIndex));
            }
            data.add(vector);
        }
        return new DefaultTableModel(data, columnNames);
    }
}

2. 正确配置Spring Boot启动类的Headless参数

打包成War后,Spring是通过SpringBootServletInitializer的configure方法启动的,原来main方法里的设置不会生效,所以要在configure方法里补充配置:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({"service.my.rest.app"})
public class ConnectionTest extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(ConnectionTest.class);
        builder.headless(false);
        builder.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // War部署时,这里的配置才会生效
        return application.sources(ConnectionTest.class).headless(false);
    }
}

3. 配置外部Tomcat允许图形环境

即使代码里设置了,有些Tomcat环境还是会强制Headless,这时候需要修改Tomcat的启动参数:

  • 找到Tomcat目录下的bin/catalina.bat(Windows)或bin/catalina.sh(Linux)
  • 在文件开头添加:
    # Linux/Mac
    CATALINA_OPTS="-Djava.awt.headless=false"
    
    # Windows
    set CATALINA_OPTS=-Djava.awt.headless=false
    
  • 或者在Tomcat的管理控制台(如果有)添加JVM启动参数-Djava.awt.headless=false

为什么原来的设置没用?

  • War部署时,外部Tomcat是容器,Spring Boot应用作为Web应用被加载,main方法不会被执行,所以你在main里的builder.headless(false)完全没起作用。
  • 即使你在configure里设置了headless=false,只要Spring容器初始化时要创建继承JFrame的Controller Bean,就会在Tomcat的Headless环境下尝试创建图形组件,直接抛出异常——这也是为什么必须分离Swing组件和Controller的核心原因。

内容的提问来源于stack exchange,提问作者Vallapu Charan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:49:01