Спасибо за все советы, вот решение, которое я в итоге придумал. Это оказалось проще, чем я думал. В этом решении используются как AspectJ, так и аннотации. Это работает так: просто добавьте одну из аннотаций (определенных ниже) к методу или классу, и простая проверка на нарушения правил EDT будет вставлена в нее в начале. Особенно, если вы помечаете таким образом целые классы, вы можете провести много тестирования с помощью лишь крошечного количества дополнительного кода.
Сначала я загрузил AspectJ и добавил его в свой проект (в eclipse вы можете использовать AJDT)
Затем я определил две новые аннотации:
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Indicates that this class or method should only be accessed by threads
* other than the Event Dispatch Thread
* <p>
* Add this annotation to methods that perform potentially blocking operations,
* such as disk, network or database access.
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface WorkerThreadOnly {}
и
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Indicates that this class or method should only be accessed by the
* Event Dispatch Thread
* <p>
* Add this annotation to methods that call (swing) GUI methods
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface EventDispatchThreadOnly {}
После этого я определил Аспект, который выполняет фактическую проверку:
import javax.swing.SwingUtilities;
/** Check methods / classes marked as WorkerThreadOnly or EventDispatchThreadOnly */
public aspect ThreadChecking {
/** you can adjust selection to a subset of methods / classes */
pointcut selection() : execution (* *(..));
pointcut edt() : selection() &&
(within (@EventDispatchThreadOnly *) ||
@annotation(EventDispatchThreadOnly));
pointcut worker() : selection() &&
(within (@WorkerThreadOnly *) ||
@annotation(WorkerThreadOnly));
before(): edt() {
assert (SwingUtilities.isEventDispatchThread());
}
before(): worker() {
assert (!SwingUtilities.isEventDispatchThread());
}
}
Теперь добавьте @EventDispatchThreadOnly или @WorkerThreadOnly к методам или классам, которые должны быть ограничены потоками. Не добавляйте ничего к потокобезопасным методам.
Наконец, просто запустите с включенными утверждениями (параметр JVM -ea), и вы скоро узнаете, где есть нарушения, если таковые имеются.
Для справки вот решение Александра Поточкина, о котором говорил Марк. Это аналогичный подход, но он проверяет вызовы методов Swing из вашего приложения, а не вызовы внутри вашего приложения. Оба подхода дополняют друг друга и могут использоваться вместе.
import javax.swing.*;
aspect EdtRuleChecker {
private boolean isStressChecking = true;
public pointcut anySwingMethods(JComponent c):
target(c) && call(* *(..));
public pointcut threadSafeMethods():
call(* repaint(..)) ||
call(* revalidate()) ||
call(* invalidate()) ||
call(* getListeners(..)) ||
call(* add*Listener(..)) ||
call(* remove*Listener(..));
//calls of any JComponent method, including subclasses
before(JComponent c): anySwingMethods(c) &&
!threadSafeMethods() &&
!within(EdtRuleChecker) {
if(!SwingUtilities.isEventDispatchThread() &&
(isStressChecking || c.isShowing()))
{
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature());
System.err.println();
}
}
//calls of any JComponent constructor, including subclasses
before(): call(JComponent+.new(..)) {
if (isStressChecking && !SwingUtilities.isEventDispatchThread()) {
System.err.println(thisJoinPoint.getSourceLocation());
System.err.println(thisJoinPoint.getSignature() +
" *constructor*");
System.err.println();
}
}
}
person
amarillion
schedule
06.09.2009