简体   繁体   中英

Unexplained field value change

Take a look at this strange issue:

调试步骤

  1. I have a breakpoint on all access to the field res1
  2. Res1 is assigned the value of f, which is "bar"
  3. Res1 now is "bar"
  4. 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.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
    }

}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM