How To Acess Target Object Behind a Spring Proxy

When annotating spring managed beans with stuff like @Transactional, spring will behind the scenes produce code, that ensures that transaction logic is applied before and after your code. Depending on the configuration, this is often done using a JDK proxy, which is a dynamically generated class implementing the bean interfaces. This dynamically generated code will apply its logic and then dispatch the call on to what is called the “target object”, that is, the object that has been proxied.

But sometimes, it would be nice to have direct access to the instance behind the proxy. In my case, it was in a test case, where I wanted to inject one dependency out of many, with a stub implementation. I am using the spring AbstractJpaTests class, which will inject proxied dependencies of beans into my testcase. All the dependencies of the injected, proxied class has also been setup by spring from the context, but I would like to set one specific of them to a stub implementation.

Problem is, the setters for dependency injection is not on the bean interface, and as such, I cannot call them on the bean instance. I also cannot cast it to the implementation class, because in my case, spring is applying the aspect using a JDK proxy, hence it is not the the implementation type, only the interface type.

Here is the code I ended up with for getting the target object:

  @SuppressWarnings({"unchecked"})
  protected <T> T getTargetObject(Object proxy, Class<T> targetClass) throws Exception {
    if (AopUtils.isJdkDynamicProxy(proxy)) {
      return (T) ((Advised)proxy).getTargetSource().getTarget();
    } else {
      return (T) proxy; // expected to be cglib proxy then, which is simply a specialized class
    }
  }

I found out (by debugger inspection), that the proxy class that spring generates implements Advised, which contains the getTargetSource() method. And on a TargetSource, one can get the actual target. I am not sure if all spring proxies always implement Advised, but it seems to do in my code :-)

I am using the code like this:

  @Override
  protected void onSetUp() throws Exception {
    getTargetObject(fooBean, FooBeanImpl.class).setBarRepository(new MyStubBarRepository());
  }

One question: The targetClass parameter on the above getTargetObject method is not used within the method itself. But I cannot find another good way to pass the type paramter into the method. Can you?

Another question: Are there any other good ways, from a test, to inject one dependency on a spring managed bean?

June 5, 2009 В· polesen В· 12 Comments
Tags: ,  В· Posted in: Programming

12 Responses

  1. Jeoffrey - July 10, 2009

    Thanks for the example it’s exactly what I’m looking for.

    I might have an answer to your first question.
    You can remove the targetClass parameter from the getTargetObject method. You would have to call the getTargetObject as follows.

    @Override
    protected void onSetUp() throws Exception {
    this.getTargetObject(fooBean).setBarRepository(new MyStubBarRepository());
    }

    Greetings

  2. polesen - July 10, 2009

    Jeoffrey, glad it could help you.

    I don’t follow about removing the targetClass param. If I do so, the type parameter T does not resolve to the type of the bean, hence I cannot call setBarRepository on it. If you have it working without that extra param together with a type param, could you the post the getTargetObject method here?

  3. Jeoffrey - July 10, 2009

    Ah I see there is something missing in my code snippet, probably filtered because it looks like xml/html :-)
    Lets see if this will work.

    @Override
    protected void onSetUp() throws Exception {
    this.<FooBeanImpl>getTargetObject(fooBean).setBarRepository(new MyStubBarRepository());
    }

  4. polesen - July 10, 2009

    Cool. That really works, though it is kind of strange syntax.

    Sometimes I have the feeling that Java generics do really shitty things to code readability.

  5. Jeoffrey - July 10, 2009

    You could also write it in a more readable way like this.

    @Override
    protected void onSetUp() throws Exception {
    FooBeanImpl fooBeanImpl = getTargetObject(fooBean);
    fooBeanImpl.setBarRepository(new MyStubBarRepository());
    }

  6. polesen - July 10, 2009

    Nice!

  7. jcesarperez - June 3, 2010

    Brave! Great explanation and great solution for the problem.

    Thank you very much.
    Julio.

  8. logan - June 7, 2010

    You have have spring generate a proxy that is a true subclass of your bean.

    Make sure that version 2.2 of cglib is on your class path. Once you have that, you need to force spring to generate a subclass of your bean. This is done with the following attribute in your config xml.

    Now the bean can be cast to the appropriate class without any AOP silliness. You can read more about it here.

    http://static.springsource.org/spring/docs/2.5.x/reference/aop.html#aop-proxying

  9. Bertolami - May 10, 2011

    Suggest you to use recursion because the proxies may be nested:

    @SuppressWarnings({“unchecked”})
    private T getTargetObject(Object proxy, Class targetClass) throws Exception {
    while( (AopUtils.isJdkDynamicProxy(proxy))) {
    return (T) getTargetObject(((Advised)proxy).getTargetSource().getTarget(), targetClass);
    }
    return (T) proxy; // expected to be cglib proxy then, which is simply a specialized class
    }

  10. Unit testing transactions and optimistic locking with Spring and JUnit | freestyle developments - January 24, 2012

    [...] default – yes I realise you can get Spring to proxy the target class using CGLIB). I found this blog post which explains how to get at the target object behind a Spring proxy. I converted it to [...]

  11. Carlos - January 9, 2013

    “Sometimes I have the feeling that Java generics do really shitty things to code readability.”

    Lol, wait for Closures/Lambda expressions. I think this will break code readability.

  12. Rolf - June 24, 2013

    I would not prefer to use this in production code, but you just solved a unittesting problem for me. Thanks for sharing!