Marten Deinum opened SPR-14050 and commented

When using Spring AOP and in a test case you use ReflectionTestUtils.setField, this fails when using (class-based) proxies. Specifically, the value is set in the proxy itself rather than in the underlying instance.

Currently one would first have to call AopTestUtils.getUltimateTargetObject and then pass that result to the ReflectionTestUtils.setField method.

Object actualTarget = AopTestUtils.getUltimateTargetObject(injectedProxy);
ReflectionTestUtils.setField(actualTarget, "field", "new-value");

Arguably calling ReflectionTestUtils.setField on a proxied class isn't the best practice, but it would be nice if the call to AopTestUtils.getUltimateTargetObject could be embedded in the ReflectionTestUtils class.


Affects: 4.2.5

Referenced from: pull request https://github.com/spring-projects/spring-framework/pull/1006

0 votes, 5 watchers

Comment From: spring-projects-issues

Marten Deinum commented

I've created a pull request for this.

In the pull request the targetObject is unwrapped when using setField or getField the other methods for invoking methods on the object are left untouched.

Comment From: spring-projects-issues

Sam Brannen commented

Hi Marten Deinum,

Thanks for the PR.

I've fixed the bugs in the code and polished everything. You can see the results in my branch:

https://github.com/sbrannen/spring-framework/commits/SPR-14050

However, after having reviewed the work, I'm not sure that we should be making these changes to the existing methods by default. The reason is that it will break any existing code that expects the class-based proxy to return values set via ReflectionTestUtils.

Thus, I'm leaning toward not implementing this at all, or introducing overloaded versions of the affected methods that accept a boolean flag to turn on this feature.

Thoughts?

Comment From: spring-projects-issues

Juergen Hoeller commented

Hmm, is there a valid case for storing values in a CGLIB proxy instance? Feels like a mistake in any sane scenario. From that perspective, always passing values through to the actual target object doesn't seem so wrong.

That said, I'm not sure ReflectionTestUtils has to do this. After all, user code can also do this manually, and it's just about test setups.

Juergen

Comment From: spring-projects-issues

Marten Deinum commented

The main pain point is that (and I've had that on a couple of projects now) is that when you have working testcases and introduce a mechanism that introduces a proxy, for instance enabling transactions or async methods on that class. All of a sudden tests start to break, because the values aren't set, because they are now set on the proxy. And I have to agree with Juergen on this that that feels like a mistake.

It comes as a surprise to people that tests suddenly start failing.

Agree it isn't very hard to solve but it still is surpising and people not aware of the fact that proxies are in play have a hard time figuring out why this happens.

I guess it should be at least documented if it isn't going to be resolved and the overloaded methods don't seem like a bad choice to have as that makes it more explicit that proxies might be playing a part.

Comment From: spring-projects-issues

Sam Brannen commented

Hmm, is there a valid case for storing values in a CGLIB proxy instance?

Yes, in fact, there is.

Consider the current test for this new functionality:

@Test
public void setFieldAndGetFieldOnCglibProxiedInstance() throws Exception {
    ProxyFactory pf = new ProxyFactory(this.person);
    pf.setProxyTargetClass(true);
    Person proxyPerson = (Person) pf.getProxy();

    // Set reflectively via Proxy
    setField(proxyPerson, "name", "Tom");

    // Get directly from Target via getter methods
    assertEquals("name (protected field)", "Tom", this.person.getName());

    // This FAILS!
    assertEquals("name (protected field)", "Tom", proxyPerson.getName());

    // Get reflectively via Proxy
    assertEquals("Tom", getField(proxyPerson, "name"));
}

Note that this.person.getName() properly returns "Tom" as expected. However... an invocation of proxyPerson.getName() returns null.

Let's assume that proxyPerson is a Spring-managed bean with transactional support applied via AOP. In such a scenario, any component that has the proxyPerson injected as a dependency would expect invocations of this.person.getName() and proxyPerson.getName() to both return the same thing. In the very least, I would expect that clients of the proxy would see the changed state in the target; otherwise, what is the point in changing the state of the target if clients of the proxy never see the affected state?

Marten Deinum, can you please expound on your use case? Specifically, how does the functionality proposed in this issue help your tests to pass? Furthermore, doesn't the proposed functionality actually cause other tests to fail? Or do you not have any tests that interact with the target via the proxy after using reflection to change state in the target?

Juergen Hoeller, I'm looking forward to your feedback, too. ;-)

Comment From: spring-projects-issues

Sam Brannen commented

Yes, in fact, there is.

Well, let me rephrase that...

The only valid case is if the getter methods for such fields are final in the class being proxied (which is the case for the Person class in the tests). In such scenarios, the CGLIB proxy cannot override the getter methods, and that's why the aforementioned test returns null via the proxy... since getName() can only see the state of the proxy and not the target's state.

Marten Deinum, with that in mind, I don't think I need any answers to my previous questions. ;)

In summary, people just have to make sure that their getter methods are not final if they are using CGLIB proxies!

Comment From: spring-projects-issues

Sam Brannen commented

Merged into master in the following commit:

https://github.com/spring-projects/spring-framework/commit/9439a008d7a4ec7359fc024fce60453995f2ebd1