Java 如何检查Swing应用程序是否正确使用EDT(事件调度线程)
我已经找到了许多正确使用EDT的教程和示例,但是我想听听人们应该如何反过来:检查一个具有Swing GUI和许多涉及长网络操作的功能的复杂应用程序,并找出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的错误使用
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更感兴趣