👋 你好,欢迎来到我的博客!我是【菜鸟不学编程】
   我是一个正在奋斗中的职场码农,步入职场多年,正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上,我决定记录下自己的学习与成长过程,也希望通过博客结识更多志同道合的朋友。
  
  🛠️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等,也会分享一些踩坑经历与面试复盘,希望能为还在迷茫中的你提供一些参考。
  💡 我相信:写作是一种思考的过程,分享是一种进步的方式。
  
   如果你和我一样热爱技术、热爱成长,欢迎关注我,一起交流进步!

I. Beans 概念:可重用组件的设计模式

JavaBeans 的核心不是“写几个 get/set”,而是一套让组件可被复用、可被工具检查、可被序列化/持久化、可被可视化编辑的约定体系。

一个合格的 Bean,通常具备这些特征:

  1. 公有无参构造(工具要能 new 出来)
  2. 属性(Property):符合命名约定的 Getter/Setter(工具要能读写)
  3. 事件(Event):标准 Listener 注册/注销方法(工具要能挂事件)
  4. 内省(Introspection):工具通过反射/BeanInfo 获取属性、事件、方法元信息
  5. 可持久化:支持用某种形式保存并恢复对象状态(XMLEncoder/XMLDecoder)

你可以把它理解成:

JavaBeans = “组件的可发现性 + 可配置性 + 可工具化”
它让“人写的类”变成“工具也看得懂的组件”。

II. 属性访问:Getter/Setter 与 PropertyChangeListener

先说最常见的:属性。JavaBeans 对属性命名约定非常严格——严格到你会怀疑它是不是来折磨人的(其实是为了让工具能一致识别)。

1)属性命名约定(别跟它硬刚)

  • 普通属性:getX() / setX(...)
  • boolean 属性:isX() / setX(...)(也可 getX(),但习惯是 isX()

示例:一个可配置按钮的属性我们设定为:

  • text:按钮文字
  • themeColor:主题色(为了简单用 String,也可以用 Color
  • enabled:是否可用(boolean)
  • padding:内边距(int)

2)PropertyChangeSupport:让属性变化“能被听见”

如果你想让外部(例如属性面板)在属性变化时刷新 UI,就要用 JavaBeans 的标准通知机制:PropertyChangeListener

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.JButton;

public class ConfigurableButton extends JButton {
    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    private String themeColor = "blue";
    private int padding = 8;

    public ConfigurableButton() {
        super("Click Me");
        applyStyle();
    }

    // --- text 属性其实 JButton 已经有 setText/getText,这里演示做一个封装也行 ---
    @Override
    public void setText(String text) {
        String old = getText();
        super.setText(text);
        pcs.firePropertyChange("text", old, text);
    }

    public String getThemeColor() {
        return themeColor;
    }

    public void setThemeColor(String themeColor) {
        String old = this.themeColor;
        this.themeColor = themeColor;
        applyStyle();
        pcs.firePropertyChange("themeColor", old, themeColor);
    }

    public int getPadding() {
        return padding;
    }

    public void setPadding(int padding) {
        int old = this.padding;
        this.padding = padding;
        applyStyle();
        pcs.firePropertyChange("padding", old, padding);
    }

    @Override
    public void setEnabled(boolean enabled) {
        boolean old = isEnabled();
        super.setEnabled(enabled);
        pcs.firePropertyChange("enabled", old, enabled);
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        pcs.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        pcs.removePropertyChangeListener(l);
    }

    private void applyStyle() {
        // 演示用:真实项目可映射到 Color、UIManager、LookAndFeel
        setOpaque(true);
        setBorderPainted(false);
        setFocusPainted(false);

        int p = Math.max(0, padding);
        setBorder(javax.swing.BorderFactory.createEmptyBorder(p, p * 2, p, p * 2));
    }
}

这里的“人味儿”在于:属性变化不只是“赋值”,它是一个事件
当你把这套机制做好,后面做“可配置 UI 组件”的体验会非常顺——属性面板改一下,界面立刻响应,像在玩真正的组件编辑器一样。

III. 事件处理:EventListener 与适配器

JavaBeans 对“事件”的约定同样很规矩:

  • 事件类型:XxxEvent
  • 监听器接口:XxxListener extends EventListener
  • 注册/注销:addXxxListener / removeXxxListener

Swing 自带很多监听器(ActionListenerMouseListener 等),这也是 Swing 和 JavaBeans 天生契合的原因。

1)定义一个自定义事件:主题切换事件

假设我们希望当 themeColor 改变时,除了 PropertyChange,还能发一个更语义化的事件:

import java.util.EventObject;

public class ThemeChangeEvent extends EventObject {
    private final String oldTheme;
    private final String newTheme;

    public ThemeChangeEvent(Object source, String oldTheme, String newTheme) {
        super(source);
        this.oldTheme = oldTheme;
        this.newTheme = newTheme;
    }

    public String getOldTheme() { return oldTheme; }
    public String getNewTheme() { return newTheme; }
}

监听器:

import java.util.EventListener;

public interface ThemeChangeListener extends EventListener {
    void themeChanged(ThemeChangeEvent e);
}

在组件里实现注册/触发:

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ConfigurableButton extends javax.swing.JButton {
    // ... 前面省略
    private final List<ThemeChangeListener> themeListeners = new CopyOnWriteArrayList<>();

    public void addThemeChangeListener(ThemeChangeListener l) {
        themeListeners.add(l);
    }

    public void removeThemeChangeListener(ThemeChangeListener l) {
        themeListeners.remove(l);
    }

    @Override
    public void setText(String text) { /*同前*/ }

    public void setThemeColor(String themeColor) {
        String old = this.themeColor;
        this.themeColor = themeColor;
        applyStyle();

        pcs.firePropertyChange("themeColor", old, themeColor);

        ThemeChangeEvent ev = new ThemeChangeEvent(this, old, themeColor);
        for (ThemeChangeListener l : themeListeners) {
            l.themeChanged(ev);
        }
    }
}

2)适配器(Adapter):救命的“少实现几个方法”

当监听器方法很多(例如 MouseListener 有 5 个方法),你只关心其中一个,这时“适配器”模式就出场了。

Swing 里直接给你准备好了:MouseAdapterKeyAdapter 等。

button.addMouseListener(new java.awt.event.MouseAdapter() {
    @Override
    public void mouseEntered(java.awt.event.MouseEvent e) {
        System.out.println("hover!");
    }
});

适配器这东西不炫,但很实用:把“接口的完整性”留给框架,把“我只关心这一点点”留给你自己

IV. Introspector:BeanInfo 与属性描述

好了,重点来了:工具是怎么知道你有哪些属性、事件、方法的?
答案:内省(Introspection)。

Java 提供了 Introspector,能根据命名约定反射出 Bean 的属性信息。

1)用 Introspector 看看我们的组件暴露了啥

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class IntrospectDemo {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(ConfigurableButton.class);

        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println("Property: " + pd.getName()
                    + ", type=" + (pd.getPropertyType() == null ? "null" : pd.getPropertyType().getSimpleName())
                    + ", read=" + (pd.getReadMethod() == null ? "null" : pd.getReadMethod().getName())
                    + ", write=" + (pd.getWriteMethod() == null ? "null" : pd.getWriteMethod().getName()));
        }
    }
}

你会看到一堆从 JButton 继承来的属性,也会看到你新增的 themeColorpadding
这就是为什么“命名约定”那么重要:工具就是靠这个在猜你的意图。

2)BeanInfo:别让工具“猜”,你可以“告诉它”

如果你不想让工具暴露一堆继承来的乱七八糟属性,或者你想自定义属性显示名、描述、编辑器,你就可以写 BeanInfo

约定:YourBean 对应 YourBeanBeanInfo

import java.beans.*;

public class ConfigurableButtonBeanInfo extends SimpleBeanInfo {
    @Override
    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            PropertyDescriptor theme = new PropertyDescriptor("themeColor", ConfigurableButton.class);
            theme.setDisplayName("Theme Color");
            theme.setShortDescription("Theme name like blue/red/green");

            PropertyDescriptor padding = new PropertyDescriptor("padding", ConfigurableButton.class);
            padding.setDisplayName("Padding");
            padding.setShortDescription("Inner padding in px");

            return new PropertyDescriptor[]{ theme, padding };
        } catch (IntrospectionException e) {
            throw new RuntimeException(e);
        }
    }
}

这一步很关键,它让你的组件从“可用”升级到“可被优雅地配置”。
不然属性面板里一堆 alignmentXverifyInputWhenFocusTarget,你自己都不想点开第二次。😂

V. 持久化:XMLEncoder 与 XMLDecoder

这部分是 JavaBeans 的另一个隐藏技能:用 XML 保存一个 Bean 的状态,然后再把它恢复出来
它对“可配置 UI 组件”特别有意义:你可以把用户在设计器里调好的组件配置保存到文件,下一次打开直接还原。

前提:

  • Bean 有无参构造
  • 属性可以通过 setter 设回去
  • 属性类型本身也要可编码(基本类型、String、可序列化/可持久化对象更稳)

1)编码保存

import java.beans.XMLEncoder;
import java.io.FileOutputStream;

public class SaveDemo {
    public static void main(String[] args) throws Exception {
        ConfigurableButton btn = new ConfigurableButton();
        btn.setText("Deploy");
        btn.setThemeColor("green");
        btn.setPadding(12);
        btn.setEnabled(true);

        try (XMLEncoder enc = new XMLEncoder(new FileOutputStream("button.xml"))) {
            enc.writeObject(btn);
        }

        System.out.println("Saved to button.xml");
    }
}

2)解码恢复

import java.beans.XMLDecoder;
import java.io.FileInputStream;

public class LoadDemo {
    public static void main(String[] args) throws Exception {
        try (XMLDecoder dec = new XMLDecoder(new FileInputStream("button.xml"))) {
            ConfigurableButton btn = (ConfigurableButton) dec.readObject();
            System.out.println("Loaded: text=" + btn.getText()
                    + ", theme=" + btn.getThemeColor()
                    + ", padding=" + btn.getPadding()
                    + ", enabled=" + btn.isEnabled());
        }
    }
}

是不是有点像“组件级的配置快照”?
但我要提醒一句很现实的话:XMLEncoder/XMLDecoder 对复杂对象、集合、匿名类、某些 UI 状态并不总是友好
做产品级持久化时,很多团队会改用 JSON/YAML + 自己的模型对象;但作为 JavaBeans 规范的示范,它非常直观,也很有“老派工程味儿”。

VI. 示例:构建一个可配置的 UI 组件(带一个“迷你属性面板”)

光有 Bean 还不够,我们来做一个小 demo:左边是按钮,右边是“属性面板”,改一下就实时生效——这就是 JavaBeans 最经典的使用姿势。

import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class BeanUiDemo {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            ConfigurableButton button = new ConfigurableButton();
            button.setText("Run");

            JFrame f = new JFrame("JavaBeans Configurable UI Demo");
            f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            f.setSize(760, 420);
            f.setLayout(new BorderLayout(12, 12));

            // 左侧预览区
            JPanel preview = new JPanel(new GridBagLayout());
            preview.setBorder(BorderFactory.createTitledBorder("Preview"));
            preview.add(button);

            // 右侧属性面板
            JPanel panel = new JPanel();
            panel.setBorder(BorderFactory.createTitledBorder("Properties"));
            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

            JTextField textField = new JTextField(button.getText());
            JTextField themeField = new JTextField(button.getThemeColor());
            JSpinner paddingSpinner = new JSpinner(new SpinnerNumberModel(button.getPadding(), 0, 64, 1));
            JCheckBox enabledBox = new JCheckBox("Enabled", button.isEnabled());

            panel.add(labeled("Text", textField));
            panel.add(Box.createVerticalStrut(8));
            panel.add(labeled("Theme Color", themeField));
            panel.add(Box.createVerticalStrut(8));
            panel.add(labeled("Padding", paddingSpinner));
            panel.add(Box.createVerticalStrut(8));
            panel.add(enabledBox);

            // 绑定交互:面板 -> Bean
            textField.addActionListener(e -> button.setText(textField.getText()));
            themeField.addActionListener(e -> button.setThemeColor(themeField.getText()));
            paddingSpinner.addChangeListener(e -> button.setPadding((Integer) paddingSpinner.getValue()));
            enabledBox.addActionListener(e -> button.setEnabled(enabledBox.isSelected()));

            // Bean -> 面板(监听属性变化,保持一致)
            button.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    switch (evt.getPropertyName()) {
                        case "text" -> textField.setText(button.getText());
                        case "themeColor" -> themeField.setText(button.getThemeColor());
                        case "padding" -> paddingSpinner.setValue(button.getPadding());
                        case "enabled" -> enabledBox.setSelected(button.isEnabled());
                    }
                }
            });

            f.add(preview, BorderLayout.CENTER);
            f.add(panel, BorderLayout.EAST);
            f.setVisible(true);
        });
    }

    private static JComponent labeled(String name, JComponent field) {
        JPanel p = new JPanel(new BorderLayout(6, 6));
        p.add(new JLabel(name), BorderLayout.WEST);
        p.add(field, BorderLayout.CENTER);
        p.setMaximumSize(new Dimension(320, 36));
        return p;
    }
}

看到这里,其实 JavaBeans 的整条链路就闭环了:

  • 组件ConfigurableButton
  • 属性:Getter/Setter + PropertyChangeSupport
  • 事件:可选自定义 Listener(或者复用 Swing 的)
  • 内省:工具/面板能读出属性
  • 持久化:可保存为 XML,下次恢复

这就是“可配置 UI 组件”的最小形态。你要把它做成设计器、做成低代码组件库,也就是在这个骨架上加皮加肉。

一点“工程味”的建议(不站队,但很实在)

如果你真要在项目里用 JavaBeans 思路做“配置化 UI”,我建议你记住三条:

  1. 属性要可控:别把所有继承来的属性都暴露给用户,用 BeanInfo 做筛选
  2. 变更要可追踪PropertyChangeListener 不只是 UI 刷新,还可以做审计/撤销重做(Undo/Redo)
  3. 持久化要现实:XMLEncoder 适合 demo/工具链,复杂场景考虑 JSON/YAML + 自己的模型更稳

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 Java 老兵
📅 日期:2026-01-07
🧵 本文原创,转载请注明出处。

更多推荐