Swing中的事件调度filter链

我正在尝试组织EventQueue.dispatchEvent的 filter链 。 像java.io.FilterInputStream或javax.servlet.Filter之类的东西。

发现EventQueueDelegate.Delegate是为了这个吗?..但是在EventQueue.dispatchEvent EventQueueDelegate.Delegateexception的情况下,它对此一无所知,并且场景中出现丑陋的java.awt.EventDispatchThread .handleException

  • 自Java SE 1.1以来,这个“临时黑客”还没有解决吗???

我也期待EventQueue.dispatchEvent调用链。 但它似乎不适合这个,因为这种方法受到保护,它需要额外的手鼓舞以使事情有效并且代码变得不那么可爱。

  • 更好的解决方案?

接下来是在EventQueueDelegate.Delegate周围与手鼓一起跳舞……

AwtExceptionHandler.java

 package example; /** * @see java.awt.EventDispatchThread#handleException(Throwable thrown) */ public interface AwtExceptionHandler { void handle(Throwable t) throws Throwable; } 

FilterEventQueueDelegate.java

 package example; import java.awt.AWTEvent; import java.awt.EventQueue; import java.lang.reflect.Method; import java.util.ConcurrentModificationException; import sun.awt.EventQueueDelegate; /** * Aims to organise filter chain of {@link EventQueueDelegate.Delegate}. * * 
 * private static final AwtResponsivenessMonitor instance = FilterEventQueueDelegate.chain(new AwtResponsivenessMonitor()); * 

* * @author Mykhaylo Adamovych */ public abstract class FilterEventQueueDelegate implements EventQueueDelegate.Delegate, AwtExceptionHandler { public static final class ExceptionHandler { private static AwtExceptionHandler currentExceptionHandler; public void handle(Throwable t) throws Throwable { currentExceptionHandler.handle(t); } } private static final class SimpleFilterEventQueueDelegate extends FilterEventQueueDelegate { private EventQueueDelegate.Delegate thirdPartyDelegate; private Object thirdPartyExceptionHandler; @Override public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException { if (thirdPartyDelegate != null) thirdPartyDelegate.afterDispatch(arg0, arg1); } @Override public Object beforeDispatch(AWTEvent arg0) throws InterruptedException { if (thirdPartyDelegate != null) return thirdPartyDelegate.beforeDispatch(arg0); return arg0; } @Override public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException { if (thirdPartyDelegate != null) return thirdPartyDelegate.getNextEvent(arg0); return arg0.getNextEvent(); } @Override public void handle(Throwable t) throws Throwable { if (thirdPartyExceptionHandler != null) try { Class c = thirdPartyExceptionHandler.getClass(); Method m = c.getMethod("handle", new Class[] { Throwable.class }); m.invoke(thirdPartyExceptionHandler, new Object[] { t }); } catch (Throwable x) { thirdPartyExceptionHandler = null; /* Do not try this again */ throw t; } else throw t; } public void setEventQueueDelegate(EventQueueDelegate.Delegate delegate) { thirdPartyDelegate = delegate; } public void setExceptionHandler(Object exceptionHandler) { thirdPartyExceptionHandler = exceptionHandler; } } public static T chain(T delegate) { synchronized (EventQueueDelegate.class) { EventQueueDelegate.Delegate currentDelegate = EventQueueDelegate.getDelegate(); FilterEventQueueDelegate currentFilterDelegate = null; if (currentDelegate instanceof FilterEventQueueDelegate) currentFilterDelegate = (FilterEventQueueDelegate) currentDelegate; else { SimpleFilterEventQueueDelegate simpleFilterDelegate = new SimpleFilterEventQueueDelegate(); if (currentDelegate != null) simpleFilterDelegate.setEventQueueDelegate(currentDelegate); Object currentExceptionHandler = null; try { currentExceptionHandler = Class.forName(System.getProperty("sun.awt.exception.handler")).newInstance(); } catch (Exception e) { } if (currentExceptionHandler != null) simpleFilterDelegate.setExceptionHandler(currentExceptionHandler); System.setProperty("sun.awt.exception.handler", ExceptionHandler.class.getName()); currentFilterDelegate = simpleFilterDelegate; } delegate.setNext(currentFilterDelegate); EventQueueDelegate.setDelegate(delegate); if (EventQueueDelegate.getDelegate() != delegate) throw new ConcurrentModificationException(); ExceptionHandler.currentExceptionHandler = delegate; return delegate; } } protected FilterEventQueueDelegate next; @Override public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException { next.afterDispatch(arg0, arg1); } @Override public Object beforeDispatch(AWTEvent arg0) throws InterruptedException { return next.beforeDispatch(arg0); } @Override public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException { return next.getNextEvent(arg0); } @Override public void handle(Throwable t) throws Throwable { next.handle(t); } private void setNext(FilterEventQueueDelegate eventQueueDelegate) { next = eventQueueDelegate; } }

AwtResponsivenessMonitor.java

 package example; import java.awt.AWTEvent; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Monitors {@code EventDispatchThread} responsiveness. * 

* Singleton is initialised on first access. * * @author Mykhaylo Adamovych */ public class AwtResponsivenessMonitor extends FilterEventQueueDelegate { private static final class DeamonThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread result = new Thread(r); result.setName(AwtResponsivenessMonitor.class.getSimpleName()); result.setDaemon(true); return result; } } private static final class NotResponsive extends RuntimeException { private static final long serialVersionUID = -1445765918431458354L; } public static final long DEFAULT_RESPONSIVENESS_TIMEOUT_S = 2; public static final long RESPONSIVENESS_WATCHDOG_MS = 50; private static final AwtResponsivenessMonitor instance = FilterEventQueueDelegate.chain(new AwtResponsivenessMonitor()); public static AwtResponsivenessMonitor getInstance() { return instance; } public static long getResponsivenessTimeout() { return instance.responsivenessTimeoutMs.get(); } public static void setResponsivenessTimeout(long timeoutMs) { instance.responsivenessTimeoutMs.set(timeoutMs); } private final AtomicLong responsivenessTimeoutMs = new AtomicLong(TimeUnit.SECONDS.toMillis(DEFAULT_RESPONSIVENESS_TIMEOUT_S)); private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new DeamonThreadFactory()); private long eventDispatchStartTime; private Thread currentWorkingThread; public AwtResponsivenessMonitor() { executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { checkResponsiveness(); } }, RESPONSIVENESS_WATCHDOG_MS, RESPONSIVENESS_WATCHDOG_MS, TimeUnit.MILLISECONDS); } @Override public synchronized void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException { eventDispatchStartTime = 0; super.afterDispatch(arg0, arg1); } @Override public synchronized Object beforeDispatch(AWTEvent arg0) throws InterruptedException { eventDispatchStartTime = System.currentTimeMillis(); currentWorkingThread = Thread.currentThread(); return super.beforeDispatch(arg0); } private synchronized void checkResponsiveness() { if (eventDispatchStartTime != 0 && currentWorkingThread != null && System.currentTimeMillis() > eventDispatchStartTime + responsivenessTimeoutMs.get()) { Exception e = new NotResponsive(); e.setStackTrace(currentWorkingThread.getStackTrace()); e.printStackTrace(); currentWorkingThread = null; } } @Override public synchronized void handle(Throwable t) throws Throwable { eventDispatchStartTime = 0; super.handle(t); } }

AwtIdleTracker.java

 package example; import java.awt.AWTEvent; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; import javax.swing.SwingUtilities; import sun.awt.SunToolkit; /** * Tracks {@code EventDispatchThread} idleness. * 

* Singleton is initialised on first access. * * @author Mykhaylo Adamovych */ public class AwtIdleTracker extends FilterEventQueueDelegate { public static final long DEFAULT_IDLE_TIME_TO_TRACK_MS = 1000; private static final long IDLE_TIME_WATCHDOG_MS = 10; private static final AwtIdleTracker instance = FilterEventQueueDelegate.chain(new AwtIdleTracker()); public static AwtIdleTracker getInstance() { return instance; } private volatile boolean inProgress; private final AtomicLong lastDispatchTime = new AtomicLong(0); @Override public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException { lastDispatchTime.set(System.currentTimeMillis()); inProgress = false; super.afterDispatch(arg0, arg1); } @Override public Object beforeDispatch(AWTEvent arg0) throws InterruptedException { inProgress = true; return super.beforeDispatch(arg0); } @Override public void handle(Throwable t) throws Throwable { lastDispatchTime.set(System.currentTimeMillis()); inProgress = false; super.handle(t); } public boolean isIdle() { return this.isIdle(DEFAULT_IDLE_TIME_TO_TRACK_MS); } public boolean isIdle(long idleTimeToTrackMs) { return !inProgress && SunToolkit.isPostEventQueueEmpty() && System.currentTimeMillis() > lastDispatchTime.get() + idleTimeToTrackMs; } public void waitForIdle() { waitForIdle(DEFAULT_IDLE_TIME_TO_TRACK_MS); } public void waitForIdle(long idleTimeToTrackMs) { waitForIdle(idleTimeToTrackMs, TimeUnit.DAYS.toMillis(365)); } public void waitForIdle(long idleTimeToTrackMs, long timeoutMs) { if (SwingUtilities.isEventDispatchThread()) throw new IllegalAccessError(); long staleThreshold = System.currentTimeMillis() + timeoutMs; while (!isIdle(idleTimeToTrackMs)) { if (System.currentTimeMillis() > staleThreshold) throw new RuntimeException("GUI still is not idle."); LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(IDLE_TIME_WATCHDOG_MS)); } } }

Example.java

 package example; import java.awt.AWTEvent; import java.awt.EventQueue; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import sun.awt.EventQueueDelegate; public class Example { public static class ThirdPartyEventQueueDelegate implements EventQueueDelegate.Delegate { public static final void registerEventQueueDelegate() { EventQueueDelegate.setDelegate(new ThirdPartyEventQueueDelegate()); } @Override public void afterDispatch(AWTEvent arg0, Object arg1) throws InterruptedException { System.out.println("Third party even queue delegate was not broken."); } @Override public Object beforeDispatch(AWTEvent arg0) throws InterruptedException { return arg0; } @Override public AWTEvent getNextEvent(EventQueue arg0) throws InterruptedException { return arg0.getNextEvent(); } } public static class ThirdPartyExceptionHandler { public static void registerExceptionHandler() { System.setProperty("sun.awt.exception.handler", ThirdPartyExceptionHandler.class.getName()); } public void handle(Throwable t) { System.out.println("Third party Exception handler was not broken."); } } private static boolean wasIdle = false; private static boolean isFistTime = true; public static synchronized void log(String msg) { System.out.println(new SimpleDateFormat("mm:ss.SSS").format(new Date()) + "\t" + msg); } public static void main(String[] args) { // let suppose there are some related stuff already ThirdPartyExceptionHandler.registerExceptionHandler(); ThirdPartyEventQueueDelegate.registerEventQueueDelegate(); // initialise singletons, build filter chain AwtIdleTracker.getInstance(); AwtResponsivenessMonitor.setResponsivenessTimeout(TimeUnit.SECONDS.toMillis(2)); testWaitForIdle(); // testSomeGui(); } public static void testSomeGui() { // some test with visible GUI JFrame frame = new JFrame(); frame.setSize(300, 300); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); while (true) { boolean isIdle = AwtIdleTracker.getInstance().isIdle(); if (isFistTime || wasIdle != isIdle) { isFistTime = false; wasIdle = isIdle; String msg = isIdle ? "idle" : "busy"; log("system becomes " + msg); } LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1)); } } public static void testWaitForIdle() { // some long operation SwingUtilities.invokeLater(new Runnable() { @Override public void run() { log("task started"); // throw new RuntimeException(); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5)); log("task finished"); } }); LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); log("started waiting for idle"); AwtIdleTracker.getInstance().waitForIdle(); log("stopped waiting for idle"); } } 

为了同步测试和被测试的应用程序,这里有Sun的资料:
SunToolkit #realSync()
SunToolkit #waitForIdle()