使用DocumentListener实现IPv4校验时遇异常及循环问题
解决DocumentListener在IPv4输入框中的异常与循环问题
我来帮你梳理并解决这个问题,你遇到的IllegalStateException和循环执行都是Swing监听器场景下的典型坑,咱们一步步拆解搞定:
1. 先搞懂问题根源
(1)IllegalStateException为啥会出现?
这个异常的全称是Attempt to mutate in notification,本质原因是:在DocumentListener的回调执行期间,Swing的文档处于写锁定状态。如果你的classMain方法里修改了txt_ip4class的文本内容,就会触发这个异常——因为你在文档变更的通知流程里,试图再次修改它。
你已经用了SwingUtilities.invokeLater把逻辑放到事件队列后面,这确实解决了异常,但又引入了新问题:修改文本会再次触发DocumentListener,导致逻辑反复执行,形成循环。
(2)循环执行的核心原因
当输入内容匹配IPv4正则时,classMain会修改box_ip4class的选中项;如果classMain同时还修改了txt_ip4class的文本(比如格式化IP),或者box_ip4class的变更事件又触发了修改txt_ip4class的逻辑,就会再次触发DocumentListener,无限循环下去。
2. 具体解决方案
方案一:用标志位跳过程序触发的监听器回调
最常用的方法是加一个布尔标志,标记当前是否是程序主动修改文本,此时跳过业务逻辑,避免循环:
// 定义一个类级别的标志位,标记是否是程序主动修改文本 private boolean isProgrammaticChange = false; txt_ip4class.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { logUpdate(); } @Override public void removeUpdate(DocumentEvent e) { logUpdate(); } @Override public void changedUpdate(DocumentEvent e) { logUpdate(); } void logUpdate() { // 如果是程序主动修改,直接跳过监听器逻辑 if (isProgrammaticChange) { return; } Runnable doHighlight = new Runnable() { @Override public void run() { String aTxt = txt_ip4class.getText(); // 只做一次正则匹配,避免重复计算 boolean isIpValid = aTxt.matches("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b"); try { // 设置标志位,告诉监听器接下来是程序主动修改 isProgrammaticChange = true; if (isIpValid) { classValue = 1; classW.classMain(classValue, box_ip4class, txt_ip4class, txt_rangeclass, txt_bitclass); } else { classValue = 0; classW.classMain(classValue, box_ip4class, txt_ip4class, txt_rangeclass, txt_bitclass); } } finally { // 不管有没有异常,都要重置标志位,保证后续用户输入能正常触发监听器 isProgrammaticChange = false; } } }; SwingUtilities.invokeLater(doHighlight); } });
方案二:临时移除再重新添加监听器
另一种思路是在修改文本前,先把DocumentListener移除,修改完成后再重新添加,这样程序修改文本时就不会触发监听器回调:
// 先把监听器抽成独立变量,方便后续移除和添加 DocumentListener ipDocListener = new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { logUpdate(); } @Override public void removeUpdate(DocumentEvent e) { logUpdate(); } @Override public void changedUpdate(DocumentEvent e) { logUpdate(); } void logUpdate() { Runnable doHighlight = new Runnable() { @Override public void run() { String aTxt = txt_ip4class.getText(); boolean isIpValid = aTxt.matches("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b"); // 临时移除监听器,避免程序修改文本时触发回调 txt_ip4class.getDocument().removeDocumentListener(ipDocListener); try { if (isIpValid) { classValue = 1; classW.classMain(classValue, box_ip4class, txt_ip4class, txt_rangeclass, txt_bitclass); } else { classValue = 0; classW.classMain(classValue, box_ip4class, txt_ip4class, txt_rangeclass, txt_bitclass); } } finally { // 重新添加监听器,恢复用户输入的监听 txt_ip4class.getDocument().addDocumentListener(ipDocListener); } } }; SwingUtilities.invokeLater(doHighlight); } }; // 初始给输入框添加监听器 txt_ip4class.getDocument().addDocumentListener(ipDocListener);
3. 额外优化建议
- 你原来的代码里重复写了两次完全一样的正则匹配,我已经合并成了
isIpValid变量,减少不必要的性能消耗。 - 检查下
classMain方法的逻辑:如果classValue=1时只需要设置box_ip4class的选中项,不需要修改txt_ip4class的文本,那循环的概率会大大降低——尽量避免不必要的文本修改。 - 对于
box_ip4class的itemStateChanged事件,如果这个事件里也会修改txt_ip4class的文本,那也要用类似的标志位/移除监听器的方式,避免反向触发DocumentListener形成循环。
内容的提问来源于stack exchange,提问作者Sayful Adrian
相关产品推荐
相关产品推荐

