Grazie per tutti i suggerimenti, ecco la soluzione che ho trovato alla fine. E 'stato più facile di quanto pensassi. Questa soluzione utilizza sia AspectJ che annotazioni. Funziona così: basta aggiungere una delle annotazioni (definite di seguito) a un metodo o una classe e all'inizio verrà inserito un semplice controllo per le violazioni delle regole EDT. Soprattutto se contrassegni intere classi come questa, puoi fare un sacco di test con solo una piccola quantità di codice extra.
Per prima cosa ho scaricato AspectJ e ha aggiunto al mio progetto (in Eclipse è possibile utilizzare AJDT)
Poi ho definito due nuovi NOTE:
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 {}
e
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 {}
Dopo di che , Ho definito l'Aspetto che esegue il controllo effettivo:
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());
}
}
Ora aggiungi @EventDispatchThreadOnly o @WorkerThreadOnly ai metodi o alle classi che devono essere confinati in thread. Non aggiungere nulla ai metodi thread safe.
Infine, esegui semplicemente con le asserzioni abilitate (opzione JVM -ea) e scoprirai presto le eventuali violazioni.
Per gli scopi di riferimento, ecco la soluzione di Alexander Potochkin, a cui Mark si è riferito. È un approccio simile, ma controlla le chiamate ai metodi swing dalla tua app, anziché le chiamate all'interno della tua app. Entrambi gli approcci sono complementari e possono essere utilizzati insieme.
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();
}
}
}
+1 per la domanda di creatività – KLE
sincronizzata non è uguale a thread safe. Vi consiglio caldamente di leggere le "nuove" librerie di concorrenza in java 5, specialmente i Futures sembrano essere utili per lo sviluppo di Swing, credo. –
@Jens: hai ragione, ho modificato leggermente la domanda. – amarillion