无法解释的字段值变化
看看这个奇怪的问题:
- 我对字段
res1
- Res1 被赋值为 f 的值,即“bar”
- Res1 现在是“bar”
- 在下一次中断时,res1 突然变为
null
为什么这是不可能的?
- 由于 res1 上的断点,我可以看到它根本不应该更改
- 而且它不能,因为我没有在赋值和assertEquals之间显式更改它,并且此代码是在单个 JUnit 线程中运行。
- 没有其他名为
res1
的字段或变量。
可能会发生什么?我假设这不是 JVM 中的错误,但谁知道呢。
正如乔恩·斯基特所说,问题在于实例不同。这是由这个奇怪的反射问题引起的:
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
}
}
运行测试时会发生什么:
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>
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.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>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
猜测:您在断言阶段看到的类实例与设置为“bar”时的实例不同。
不过,如果不看到其余代码,就很难判断。
编辑:问题是您的 opsByClass 缓存将包含一个操作,该操作隐式具有关联的 ServiceObject 。因此,当您使用
Foo
创建第一个ServiceObject
时,它将缓存与第一个ServiceObject
实例关联的操作实例。当您在ServiceObject
的第二个实例上调用svc.execute
时,它将在映射中查找操作,并找到与第一个实例关联的操作...这可能不是您想要的。
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 associatedServiceObject
. So when you create the firstServiceObject
with aFoo
, it will cache Operation instances associated with that first instance ofServiceObject
. When you callsvc.execute
on the second instance ofServiceObject
, it will look up the operation in the map, and find theOperation
associated with the first instance... which probably isn't what you intended.我猜你的代码跨越了不同的测试。
建议您在onSetUp()方法中进行设置
I'm guessing you've got your code spanning different tests.
Suggest you do the setup in the onSetUp() method
我找到了。问题是,因为
Operation
不是static
,它引用的是最初创建的ServiceObject
,而不是第二次调用的那个。执行于。I found it. The problem is that because
Operation
is notstatic
, it refers to theServiceObject
it was initially created by, not the one which the second call is executed upon.