Java로 데스크톱 GUI를 개발할 때 사용하는 대표적인 기술 중 하나가 Swing이다.
최근 신규 프로젝트에서 주력으로 선택되는 기술은 아니지만, Java 기반 레거시 데스크톱 애플리케이션을 유지보수하거나 기존 코드를 분석해야 한다면 여전히 접할 수 있다.
Swing을 이해할 때 중요한 것은 JButton이나 JTextField 같은 컴포넌트의 사용법을 외우는 것이 아니다.
사용자 입력 -> 이벤트 -> 상태 변경 -> UI 반영이라는 GUI 프로그램의 기본 구조와, 이 과정이 EDT(Event Dispatch Thread) 위에서 어떻게 동작하는지를 이해하는 것이 핵심이다.
Swing이란
Swing은 Java에서 데스크톱 GUI를 만들기 위한 GUI 라이브러리다.
버튼, 텍스트 입력창, 체크박스, 테이블, 메뉴 등 다양한 UI 컴포넌트를 제공한다.
간단한 Swing 애플리케이션은 JFrame을 만들고 그 안에 컴포넌트를 배치하는 형태로 구성할 수 있다.
import javax.swing.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Swing Example");
JButton button = new JButton("Click");
frame.add(button);
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
JFrame은 최상위 윈도우 역할을 하고, JPanel은 여러 컴포넌트를 묶는 컨테이너 역할을 한다.
전체 구조는 다음처럼 볼 수 있다.
JFrame -> Container / JPanel / JLabel / JButton / JTextField / JTable
실제 애플리케이션에서는 JFrame 하나에 모든 컴포넌트를 직접 넣기보다 여러 JPanel로 화면을 나누는 경우가 많다.
주요 컴포넌트
Swing에는 다양한 UI 컴포넌트가 존재하지만 기본적으로 다음 정도를 알아두면 된다.
| 컴포넌트 | 용도 |
|---|---|
JFrame | 최상위 윈도우 |
JPanel | 컴포넌트를 묶는 컨테이너 |
JLabel | 텍스트 표시 |
JButton | 버튼 |
JTextField | 한 줄 텍스트 입력 |
JTextArea | 여러 줄 텍스트 입력 |
JCheckBox | 체크 선택 |
JRadioButton | 여러 선택지 중 하나 선택 |
JComboBox | 드롭다운 |
JList | 목록 |
JTable | 테이블 |
JMenuBar | 메뉴 |
특히 JPanel은 복잡한 화면을 영역별로 나눌 때 자주 사용한다.
JFrame -> HeaderPanel / ContentPanel / FooterPanel
각 패널이 자신의 UI를 담당하도록 만들면 하나의 거대한 화면 클래스로 코드를 작성하는 것보다 유지보수가 쉬워진다.
Layout Manager
Swing에서는 컴포넌트의 위치를 직접 좌표로 지정할 수도 있지만 일반적으로 Layout Manager를 사용한다.
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(new JButton("A"));
panel.add(new JButton("B"));
panel.add(new JButton("C"));
대표적인 Layout Manager는 다음과 같다.
FlowLayout
컴포넌트를 순서대로 배치한다.
[A] / [B] / [C] / [D]
간단한 UI에 적합하다.
BorderLayout
영역을 NORTH, SOUTH, WEST, EAST, CENTER로 나누어 배치한다.
BorderLayout -> NORTH / SOUTH / WEST / EAST / CENTER
예를 들어:
panel.setLayout(new BorderLayout());
panel.add(topPanel, BorderLayout.NORTH);
panel.add(centerPanel, BorderLayout.CENTER);
panel.add(bottomPanel, BorderLayout.SOUTH);
GridLayout
행과 열로 영역을 균등하게 나눈다.
[A] / [B] / [C] / [D] / [E] / [F]
계산기 버튼처럼 동일한 크기의 컴포넌트를 배치할 때 사용할 수 있다.
GridBagLayout
복잡한 화면 배치를 세밀하게 제어할 수 있는 Layout Manager다.
대신 GridBagConstraints 등 설정이 많아 코드가 복잡해질 수 있다.
따라서 모든 화면에 무조건 사용할 필요는 없고, 필요한 경우에 선택하는 것이 좋다.
Event Listener
Swing 애플리케이션은 사용자의 입력을 이벤트로 받아 처리한다.
버튼 클릭 같은 이벤트가 발생하면 등록된 Listener가 실행된다.
JButton button = new JButton("저장");
button.addActionListener(e -> {
System.out.println("저장 버튼 클릭");
});
기본적인 구조는 다음과 같다.
User Input -> Event -> Event Listener -> Application Logic
대표적인 이벤트 리스너는 다음과 같다.
ActionListener / MouseListener / MouseMotionListener / KeyListener / WindowListener
버튼이나 메뉴처럼 특정 명령을 실행하는 UI에는 ActionListener를 가장 많이 사용한다.
Event Dispatch Thread
Swing에서 가장 중요한 개념 중 하나가 EDT(Event Dispatch Thread)다.
Swing의 UI 이벤트 처리와 대부분의 UI 작업은 EDT를 중심으로 동작한다.
GUI를 생성할 때도 일반적으로 SwingUtilities.invokeLater()를 사용한다.
SwingUtilities.invokeLater(() -> {
createAndShowGUI();
});
핵심은 EDT를 오래 점유하면 UI 전체가 멈춘다는 것이다.
예를 들어:
button.addActionListener(e -> {
heavyTask();
});
heavyTask()가 10초 동안 실행된다면 버튼 이벤트를 처리하던 EDT도 10초 동안 해당 작업에 묶인다.
Button Click -> EDT -> Heavy Task -> UI Update
그동안 EDT가 다른 이벤트를 처리하지 못하기 때문에 화면이 멈춘 것처럼 보인다.
따라서 오래 걸리는 작업은 EDT와 분리해야 한다.
User Input -> EDT -> Background Task -> Result -> EDT -> UI Update
Swing에서 GUI를 제대로 다루려면 컴포넌트 API보다 EDT와 스레드의 관계를 이해하는 것이 더 중요하다.
SwingWorker
파일 처리, 네트워크 요청, 대량 데이터 처리처럼 오래 걸리는 작업은 SwingWorker를 사용할 수 있다.
new SwingWorker<String, Void>() {
@Override
protected String doInBackground() {
return loadData();
}
@Override
protected void done() {
// 작업 완료 후 UI 갱신
}
}.execute();
doInBackground()에서는 백그라운드 작업을 수행하고 done()에서는 작업 결과를 이용해 UI를 갱신할 수 있다.
전체적인 구조는 다음과 같다.
EDT -> SwingWorker -> Background Task -> Result -> EDT -> UI Update
결국 중요한 원칙은 단순하다.
무거운 작업으로 EDT를 막지 않는다.
Component State
Swing의 UI 컴포넌트는 각각 자신의 상태를 가지고 있다.
예를 들어 JTextField에서 현재 값을 가져올 수 있다.
String name = textField.getText();
값을 변경할 수도 있다.
textField.setText("Hello");
버튼의 활성화 상태도 변경할 수 있다.
button.setEnabled(false);
따라서 Swing 애플리케이션의 동작은 다음과 같은 구조로 볼 수 있다.
User Input -> Event -> State Change -> UI Update
프로그램이 복잡해질수록 중요한 것은 컴포넌트 자체보다 상태를 어디에서 관리하고 어떻게 UI에 반영할 것인가가 된다.
Model
Swing의 일부 컴포넌트는 UI와 데이터를 분리하는 Model 구조를 사용한다.
대표적인 예가 JTable이다.
JTable -> TableModel -> Data
예를 들어:
DefaultTableModel model = new DefaultTableModel(
new Object[]{"Name", "Score"},
0
);
model.addRow(new Object[]{"Player1", 100});
model.addRow(new Object[]{"Player2", 200});
JTable table = new JTable(model);
테이블 자체와 실제 데이터를 분리하면 데이터 변경과 UI 표현을 독립적으로 관리하기 쉬워진다.
JTable, JList, JTree 같은 컴포넌트를 사용할수록 이러한 Model 구조를 이해할 필요가 있다.
화면 구조 분리
화면이 복잡해지면 하나의 JFrame에 모든 UI를 직접 넣는 구조는 빠르게 한계가 온다.
보통 JPanel을 기준으로 화면을 영역별로 분리한다.
JFrame -> HeaderPanel / ContentPanel / FooterPanel
Content 영역도 다시 나눌 수 있다.
ContentPanel -> InputPanel / TablePanel / DetailPanel
각 패널이 자신의 UI와 관련된 책임을 가지도록 하면 코드의 결합도를 낮출 수 있다.
예를 들어:
class UserPanel extends JPanel {
private final JTextField nameField = new JTextField(10);
private final JButton saveButton = new JButton("저장");
public UserPanel() {
setLayout(new FlowLayout());
add(nameField);
add(saveButton);
saveButton.addActionListener(e -> save());
}
private void save() {
String name = nameField.getText();
System.out.println(name);
}
}
화면 전체를 하나의 클래스에서 관리하는 것보다 기능별 컴포넌트와 패널로 나누는 편이 규모가 커졌을 때 유리하다.
Swing과 AWT
Swing을 이해하려면 AWT(Abstract Window Toolkit)도 함께 알아둘 필요가 있다.
Swing은 AWT의 여러 기능과 구조를 기반으로 동작한다.
AWT -> Component / Container / Window / Layout / Event
Swing에서는 이를 기반으로 더 많은 GUI 컴포넌트를 제공한다.
Swing -> JFrame / JPanel / JButton / JTable / JTextField
Swing 컴포넌트 이름 대부분에 J가 붙는 것도 이러한 계층과 관련이 있다.
둘을 완전히 별개의 기술로 생각하기보다 AWT를 기반으로 Swing이 확장되었다고 이해하는 편이 좋다.
Swing의 대표적인 문제
Swing에서 반복적으로 만나게 되는 문제는 크게 몇 가지가 있다.
EDT에서 무거운 작업 실행
가장 흔한 문제다.
Button Click -> EDT -> Heavy Task -> UI Freeze
파일 I/O나 네트워크 요청처럼 오래 걸리는 작업은 EDT와 분리해야 한다.
거대한 UI 클래스
처음에는 하나의 MainFrame에 모든 코드를 넣어도 문제가 없어 보인다.
하지만 기능이 추가되면 UI 구성, 이벤트 처리, 데이터 처리, 상태 관리가 하나의 클래스에 몰리게 된다.
이 경우 패널, 모델, 서비스 등의 역할을 분리하는 것이 좋다.
절대 좌표에 의존
button.setBounds(100, 50, 120, 40);
직접 좌표를 지정하면 간단한 화면에서는 빠르게 만들 수 있지만 창 크기 변경이나 UI 구조 변경에 취약하다.
일반적인 화면에서는 Layout Manager를 사용하는 편이 유지보수에 유리하다.
Swing의 전체 구조
Swing 애플리케이션을 하나의 구조로 보면 다음과 같이 정리할 수 있다.
User Input -> Event Listener -> Application Logic -> Model / State -> UI Component
UI 구조 자체는 다음과 같이 구성된다.
JFrame -> JPanel / JPanel / JPanel
각 패널 내부에는 필요한 UI 컴포넌트를 배치한다.
JPanel -> JLabel / JTextField / JButton / JTable
오래 걸리는 작업은 EDT와 분리한다.
EDT -> SwingWorker -> Background Task -> Result -> EDT -> UI Update
결국 Swing을 하나의 흐름으로 보면 다음과 같다.
User Input -> Event -> State / Model Change -> UI Update
여기에 스레드 처리가 추가되면:
User Input -> EDT -> Background Task -> Result -> EDT -> UI Update
이 흐름을 이해하면 개별 컴포넌트의 사용법은 필요할 때 찾아서 사용할 수 있다.
Swing의 현재 위치
Swing은 현재 신규 데스크톱 애플리케이션의 주력 선택지라고 보기는 어렵다.
하지만 Java로 만들어진 기존 데스크톱 프로그램에는 여전히 Swing이 사용되는 경우가 있고, 레거시 애플리케이션을 유지보수하거나 분석할 때는 구조를 이해하고 있어야 한다.
또한 Swing에서 배우는 내용 자체가 Swing에만 한정되는 것은 아니다.
User Input -> Event -> State Change -> UI Update
이벤트 기반 GUI 구조와 UI 상태 관리, 렌더링과 작업 스레드의 분리는 다른 GUI 프레임워크에서도 반복적으로 등장한다.
정리
Swing에서 우선 익혀야 할 핵심은 다음과 같다.
JFrame / JPanel / UI Components / Layout Manager / Event Listener / EDT / SwingWorker / Model
공부 순서는 다음 정도로 잡으면 된다.
Component -> Container -> Layout -> Event -> EDT -> Model -> 화면 구조 분리
Swing의 핵심은 버튼을 만드는 방법이 아니다.
사용자 입력 -> 이벤트 -> 상태 변경 -> UI 반영이라는 구조를 이해하고, 동시에 EDT에서 UI를 처리하면서 무거운 작업은 별도의 실행 흐름으로 분리하는 것이 중요하다.
Swing을 제대로 이해하면 단순히 오래된 Java GUI 기술 하나를 익히는 것이 아니라 이벤트 기반 데스크톱 애플리케이션의 기본 구조를 이해할 수 있다.
현재 신규 프로젝트라면 다른 기술을 검토할 수 있지만, Java GUI와 레거시 시스템을 이해한다는 관점에서는 여전히 학습 가치가 있는 기술이다.