2010-12-09 6 views
1

Jetez un oeil à cette question étrange:valeur du champ Inexpliqué changement

Debugging steps

  1. J'ai un point d'arrêt sur tous les accès au champ res1
  2. Res1 reçoit la valeur de f, qui est "bar"
  3. est maintenant Res1 "bar"
  4. Lors de la prochaine pause, RES1 est soudainement null

Pourquoi est-ce impossible?

  • En raison du point d'arrêt sur RES1, je peux voir qu'il ne devrait pas avoir changé du tout
  • Et il ne pouvait pas, parce que je ne change pas explicitement entre l'affectation et le assertEquals et ce le code s'exécute dans un seul thread JUnit. Il n'y a pas d'autres champs ou variables nommés res1.

Quoi de neuf? Je suppose que ce n'est pas un bug dans la JVM, mais qui sait. Comme Jon Skeet l'a dit, le problème est que les instances sont différentes. Cela est dû à cette question étrange de réflexion:

public class ServiceObject { 

    private static final Map<Class<?>, Map<String, Operation>> opsByClass = 
     new ConcurrentHashMap<Class<?>, Map<String,Operation>>(); 

    private final Object target; 
    private final Map<String, Operation> myClassOps; 

    private class Operation { 
     private final Method method; 
     public Operation(Method met) { 
      this.method = met; 
      method.setAccessible(true); 
     } 
     public void execute(Map<String,?> args, Responder responder, Object context, boolean coerce) { 
      ... 
      method.invoke(target, mArgs); 
     } 
    } 

    public ServiceObject(Object target) { 
     Class<?> myClass = target.getClass(); 
     Map<String, Operation> op = opsByClass.get(myClass); 
     if (op == null) { 
      op = new HashMap<String, Operation>(); 
      for (Method meth : myClass.getMethods()) { 
       ... 
       op.put(opName, new Operation(meth)); 
      } 
      opsByClass.put(myClass, op); 
     } 
     this.target = target; 
     this.myClassOps = op; 
    } 

    public void execute(String opName) { 
     Operation op = myClassOps.get(opName); 
     ... 
     op.execute(args, responder, context, coerce); 
    } 
} 

// Foo is equivalent to the class that contains <res1> above 
class Foo { 

    @ServiceOperation 
    public void bar() { 
     // breakpoint here 
    } 

} 

Que se passe lorsque les tests sont exécutés:

a = new Foo(); 
b = new Foo(); 
svc = new ServiceObject(a); 
svc.execute("bar", ...); // inside Foo.bar() <this> will be <a> 
svc = new ServiceObject(b); 
svc.execute("bar", ...); // inside Foo.bar() <this> will still be <a>, but should be <b> 
+0

Pourriez-vous également écrire du code? –

+0

Pouvez-vous le réduire à la quantité minimale de code nécessaire pour démontrer le problème et afficher le code dans son intégralité? – NPE

+0

@aix: Code affiché –

Répondre

4

A une supposition: vous êtes à la recherche à une autre instance de la classe pendant la phase d'affirmation que vous étiez quand il était réglé sur "barre".

C'est difficile à dire sans voir le reste du code.

EDIT: Le problème est que votre cache opsByClass va inclure une opération, qui a implicitement un ServiceObject associé. Ainsi, lorsque vous créez le premier ServiceObject avec un Foo, il met en cache les instances d'opération associées à cette première instance de ServiceObject. Lorsque vous appelez svc.execute sur la seconde instance de ServiceObject, il recherchera l'opération dans la carte, et trouvera le Operation associé à la première instance ... qui n'est probablement pas ce que vous vouliez.

+0

Comment n'ai-je pas pu vérifier cela? En effet, c'est le problème. Maintenant, pour savoir pourquoi c'est le cas ... –

+0

J'ai mis à jour ma question avec le code –

+0

@Bart: J'ai édité ma réponse pour expliquer ce qui se passe, autant que je peux. –

0

Je suppose que vous avez votre code couvrant différents tests.
Je vous suggère de faire la configuration dans la méthode onSetUp()

0

Je l'ai trouvé. Le problème est que parce que Operation n'est pas static, il fait référence à ServiceObject il a été initialement créé par, pas celui sur lequel le deuxième appel est exécuté.

Questions connexes