Unexplained field value change
Take a look at this strange issue:
- I have a breakpoint on all access to the field
res1
- Res1 is assigned the value of f, which is "bar"
- Res1 now is "bar"
- At the next break, res1 is suddenly
null
Why is this impossible?
- Because of the breakpoint on res1, I can see that it shouldn't have changed at all
- And it couldn't, because I don't explicitly change it between the assignment and the
assertEquals
and this code is running in a single JUnit thread. - There are no other fields or variables named
res1
.
What could be up? I'm assuming it's not a bug in the JVM, but who knows.
As Jon Skeet said, the problem is that the instances are different. This is caused by this strange reflection issue:
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.get开发者_如何学JAVAMethods()) {
...
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
}
}
What happens when the tests are run:
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>
At a guess: you're looking at a different instance of the class during the assertion phase than you were when it was set to "bar".
It's hard to tell without seeing the rest of the code though.
EDIT: The problem is that your opsByClass
cache is going to include an Operation, which implicitly has an associated ServiceObject
. So when you create the first ServiceObject
with a Foo
, it will cache Operation instances associated with that first instance of ServiceObject
. When you call svc.execute
on the second instance of ServiceObject
, it will look up the operation in the map, and find the Operation
associated with the first instance... which probably isn't what you intended.
I'm guessing you've got your code spanning different tests.
Suggest you do the setup in the onSetUp() method
I found it. The problem is that because Operation
is not static
, it refers to the ServiceObject
it was initially created by, not the one which the second call is executed upon.
精彩评论