Java 如何检查Swing应用程序是否正确使用EDT(事件调度线程)

Java 如何检查Swing应用程序是否正确使用EDT(事件调度线程),java,swing,user-interface,thread-safety,event-dispatch-thread,Java,Swing,User Interface,Thread Safety,Event Dispatch Thread,我已经找到了许多正确使用EDT的教程和示例,但是我想听听人们应该如何反过来:检查一个具有Swing GUI和许多涉及长网络操作的功能的复杂应用程序,并找出EDT使用不当的地方 我发现 SwingUtilities.isEventDispatchThread() 可以用来检查一段代码是否在EDT内,因此我可以检查所有长操作是否恰好在SwingUtilities.isEventDispatchThread()返回true的位置内 是这样吗?是否有更好的方法可以调试整个应用程序以查找EDT的错误使用

我已经找到了许多正确使用EDT的教程和示例,但是我想听听人们应该如何反过来:检查一个具有Swing GUI和许多涉及长网络操作的功能的复杂应用程序,并找出EDT使用不当的地方

我发现

SwingUtilities.isEventDispatchThread()
可以用来检查一段代码是否在EDT内,因此我可以检查所有长操作是否恰好在SwingUtilities.isEventDispatchThread()返回true的位置内

是这样吗?是否有更好的方法可以调试整个应用程序以查找EDT的错误使用? 多谢各位

是这样吗

是的,检查
SwingUtilities.isEventDispatchThread()
的值是查看代码是否在事件调度线程(EDT)上的一种方法

另一种方法是显示或打印
Thread.currentThread().getName()
。EDT的名称几乎总是“AWT-EventQueue-0”

这段漂亮的代码来自文章。但是,它不是一个完整的Swing调试器。此代码仅检查重绘冲突

本文列出了其他更完整的调试方法

import javax.swing.JComponent;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;

public class CheckThreadViolationRepaintManager extends RepaintManager {
    // it is recommended to pass the complete check
    private boolean completeCheck   = true;

    public boolean isCompleteCheck() {
        return completeCheck;
    }

    public void setCompleteCheck(boolean completeCheck) {
        this.completeCheck = completeCheck;
    }

    public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations(component);
        super.addInvalidComponent(component);
    }

    public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
        checkThreadViolations(component);
        super.addDirtyRegion(component, x, y, w, h);
    }

    private void checkThreadViolations(JComponent c) {
        if (!SwingUtilities.isEventDispatchThread()
                && (completeCheck || c.isShowing())) {
            Exception exception = new Exception();
            boolean repaint = false;
            boolean fromSwing = false;
            StackTraceElement[] stackTrace = exception.getStackTrace();
            for (StackTraceElement st : stackTrace) {
                if (repaint && st.getClassName().startsWith("javax.swing.")) {
                    fromSwing = true;
                }
                if ("repaint".equals(st.getMethodName())) {
                    repaint = true;
                }
            }
            if (repaint && !fromSwing) {
                // no problems here, since repaint() is thread safe
                return;
            }
            exception.printStackTrace();
        }
    }
}

检查整个应用程序的EDT是否正确使用的一种方法是使用java代理。下面的代码是在下发布的代理的改进版本。它适用于ASM4.1。创建一个Jar,其中包含asm-all-4.1.Jar(未打包)、编译的代码和一个将代理指定为Premain类的清单,然后开始

/**
 * A java agent which transforms the Swing Component classes in such a way that a stack
 * trace will be dumped or an exception will be thrown when they are accessed from a wrong thread.
 * 
 * To use it, add
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar
 * </pre>
 * 
 * to the VM arguments of a run configuration. This will cause the stack traces to be dumped.
 * 
 * Use
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar=throw
 * </pre>
 * to throw exceptions.
 * 
 */
public class SwingEDTCheckAgent {

    public static void premain(String args, Instrumentation inst) {
        boolean throwing = false;
        if ("throw".equals(args)) {
            throwing = true;
        }
        inst.addTransformer(new Transformer(throwing));
    }

    private static class Transformer implements ClassFileTransformer {

        private final boolean throwing;

        public Transformer(boolean throwing) {
            this.throwing = throwing;
        }

        @Override
        public byte[] transform(ClassLoader loader,
            String className,
            Class classBeingRedefined,
            ProtectionDomain protectionDomain,
            byte[] classfileBuffer)
            throws IllegalClassFormatException {
            // Process all classes in javax.swing package which names start with J
            if (className.startsWith("javax/swing/J")) {
                ClassReader cr = new ClassReader(classfileBuffer);
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                ClassVisitor cv = new EdtCheckerClassAdapter(cw, throwing);
                cr.accept(cv, 0);
                return cw.toByteArray();
            }
            return classfileBuffer;
        }
    }

    private static class EdtCheckerClassAdapter extends ClassVisitor {

        private final boolean throwing;

        public EdtCheckerClassAdapter(ClassVisitor classVisitor, boolean throwing) {
            super(Opcodes.ASM4, classVisitor);
            this.throwing = throwing;
        }

        @Override
        public MethodVisitor visitMethod(final int access,
            final String name,
            final String desc,
            final String signature,
            final String[] exceptions) {
            MethodVisitor mv =
                cv.visitMethod(access, name, desc, signature, exceptions);

            if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is")) {
                return new EdtCheckerMethodAdapter(mv, throwing);
            } else {
                return mv;
            }
        }
    }

    private static class EdtCheckerMethodAdapter extends MethodVisitor {

        private final boolean throwing;

        public EdtCheckerMethodAdapter(MethodVisitor methodVisitor, boolean throwing) {
            super(Opcodes.ASM4, methodVisitor);
            this.throwing = throwing;
        }

        @Override
        public void visitCode() {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/awt/EventQueue", "isDispatchThread", "()Z");
            Label l1 = new Label();
            mv.visitJumpInsn(Opcodes.IFNE, l1);
            Label l2 = new Label();
            mv.visitLabel(l2);

            if (throwing) {
                // more Aggressive: throw exception
                mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
                mv.visitInsn(Opcodes.DUP);
                mv.visitLdcInsn("Swing Component called from outside the EDT");
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
                mv.visitInsn(Opcodes.ATHROW);

            } else {
                // this just dumps the Stack Trace
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "dumpStack", "()V");
            }
            mv.visitLabel(l1);
        }
    }
}
/**
*一个java代理,它以堆栈
*当从错误线程访问跟踪时,将转储跟踪或引发异常。
* 
*要使用它,请添加
* 
*-javaagent:${workspace\u loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar
* 
* 
*指向运行配置的VM参数。这将导致转储堆栈跟踪。
* 
*使用
* 
*-javaagent:${workspace\u loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar=throw
* 
*抛出异常。
* 
*/
公共类SwingedCheckAgent{
公共静态无效预输入(字符串参数、指令插入){
布尔值=假;
如果(“抛出”。等于(args)){
投掷=正确;
}
inst.addTransformer(新变压器(投掷));
}
私有静态类转换器实现ClassFileTransformer{
私人决赛;
公共变压器(布尔型){
这个。投掷=投掷;
}
@凌驾
公共字节[]转换(类加载器,
字符串类名称,
类被重新定义,
ProtectionDomain ProtectionDomain,
字节[]类文件缓冲区)
抛出IllegalClassFormatException{
//处理javax.swing包中以J开头的所有类
if(className.startsWith(“javax/swing/J”)){
ClassReader cr=新的ClassReader(classfileBuffer);
ClassWriter cw=新的ClassWriter(cr,ClassWriter.COMPUTE\u帧);
ClassVisitor cv=新的EdtCheckerClassAdapter(cw,投掷);
cr.accept(cv,0);
返回cw.toByteArray();
}
返回类文件缓冲区;
}
}
私有静态类EdtCheckerClassAdapter扩展ClassVisitor{
私人决赛;
公共EdtCheckerClassAdapter(ClassVisitor ClassVisitor,布尔抛出){
超级(操作码ASM4,类访客);
这个。投掷=投掷;
}
@凌驾
公共方法访问者访问方法(最终int访问,
最后一个字符串名,
最终字符串描述,
最后的字符串签名,
最终字符串[]例外){
MethodVisitor mv=
简历访问方法(访问、姓名、描述、签名、例外);
if(name.startsWith(“set”)| name.startsWith(“get”)| name.startsWith(“is”)){
返回新的EdtCheckerMethodAdapter(mv,投掷);
}否则{
返回mv;
}
}
}
私有静态类EdtCheckerMethodAdapter扩展MethodVisitor{
私人决赛;
公共EdtCheckerMethodAdapter(MethodVisitor MethodVisitor,布尔抛出){
super(opcode.ASM4,methodVisitor);
这个。投掷=投掷;
}
@凌驾
公共无效访问代码(){
mv.visitMethodInsn(Opcodes.INVOKESTATIC,“java/awt/EventQueue”,“isDispatchThread”,“Z”);
标签l1=新标签();
mv.visitJumpInsn(操作码IFNE,l1);
标签l2=新标签();
mv.visitLabel(l2);
如果(投掷){
//更具攻击性:抛出异常
mv.visitTypeInsn(Opcodes.NEW,“java/lang/RuntimeException”);
mv.visitInsn(操作码DUP);
mv.visitLdcInsn(“从EDT外部调用的摆动组件”);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,“java/lang/RuntimeException”,“Ljava/lang/String;”)V);
mv.visitInsn(操作码ATHROW);
}否则{
//这只是转储堆栈跟踪
mv.visitMethodInsn(Opcodes.INVOKESTATIC,“java/lang/Thread”,“dumpStack”,“()V”);
}
mv.visitLabel(l1);
}
}
}

看一看:典型的检查策略是相反的:它们找到您在EDT之外访问Swing组件的位置(与检查EDT上是否没有长时间运行的代码相比)-逻辑上,如果不在您怀疑长时间运行的地方添加代码,那么后者是不可能的。如何找到您可以从EDT访问Swing组件的地方?我对techniqu更感兴趣