Announcement Announcement Module
Collapse
No announcement yet.
equals between advised object and proxy fails Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • equals between advised object and proxy fails

    Hello

    I defined an interceptor that would post an event if a certain method gets called on any instance of a class ticket. The event contains the ticket on which the method was invoked.

    My problem is, that my JUnit test fails. In the following snippet ticket is an aop proxy and te.getTicket() is the TicketImpl:
    <test>
    TicketEvent te = (TicketEvent) o;
    Assert.assertEquals("Tickets match", ticket, te.getTicket());
    </test>

    <interceptor>
    public void afterReturning(Object returnValue, Method method,
    Object[] args, Object target) throws Throwable
    {
    if(target != null)
    {
    controllerM.postEvent(new TicketEvent((Ticket) target)); <-- target is the unadvised object
    </interceptor>

    Tracing the spring code i detected that the springframework only executes equals if the comparison object is proxied. So this fails
    Assert.assertEquals("Tickets match", ticket, te.getTicket());
    but the reverse comparison works:
    Assert.assertEquals("Tickets match", te.getTicket(), ticket);

    Is this working as intended?


    Sidenote: in this case i am glad the error showed up, since i did not want to post an unadviced reference throughout the application :shock:

  • #2
    Add: using spring 1.15

    Comment


    • #3
      Sorry for digging up this old thread, but I came across the same problem in a slightly different context.

      In our context we have a domain objects (lets call the class MyDomainObject). Because we use AOP proxies I wanted the equals method to be able to accept subclasses such as proxies of the domain object as well, so I implemented the equals method of the domain object as following:

      Code:
      /**
       * {@inheritDoc}
       */
      @Override
      public boolean equals(final Object obj) {
      	if (this == obj) {
      		return true;
      	}
      	if (obj == null) {
      		return false;
      	}
      	// allow subclasses
      	if (!getClass().isAssignableFrom(obj.getClass())) {
      		return false;
      	}
      	final MyDomainObject other = (MyDomainObject) obj;
      	if (id != other.id) {
      		return false;
      	}
      	return true;
      }
      One could argue though that the isAssignableFrom() call instead of getClass() != obj.getClass() could break the equals symmetry. Anyway. The following JUnit test succeeds:

      Code:
      @Test
      public void testObjectEquality() {
      
      	final MyDomainObject object = new MyDomainObject();
      
      	// now create a proxy to the object
      	final ProxyFactory factory = new ProxyFactory(object);
      
      	// this calls is necessary in order to force CGLIB being used. Otherwise
      	// only interfaces are allowed
      	factory.setProxyTargetClass(true);
      
      	final MyDomainObject proxyDomainObject = (MyDomainObject) factory
      			.getProxy();
      
      	assertTrue(object.equals(proxyDomainObject));
      
      	// Following assertion will fail due to the Spring Aop EqualsInterceptor
      	// that requires the other object being a proxy as well!
      	// assertTrue(proxyDomainObject.equals(object));
      }
      ... because we call equals on the non-proxied object. If we swap the objects in the assertion so that we get the assertion as shown in the code above (the commented line)
      Code:
      assertTrue(proxyDomainObject.equals(object));
      ... the test will fail. That's because the Cglib2AopProxy.EqualsInterceptor kicks in and returns false because the argument object is not a proxy (of type Factory to be precise).

      I'm wondering if it could be worth a consideration in the EqualsInterceptor?

      Comment


      • #4
        Did you solve this problem? If so, how did you do it?

        Comment


        • #5
          Hm my last post was about one and a half years ago, I'm not sure how exactly I solved this. But looking back I remember I didn't do anything particular. I just left the test code as it was using the equals method as above. Retrospectively however I think this isn't exactly clean. But anyway, that's a different topic.
          Do you have the exact same problem?

          Comment


          • #6
            Originally posted by tomotronic View Post
            Hm my last post was about one and a half years ago, I'm not sure how exactly I solved this. But looking back I remember I didn't do anything particular. I just left the test code as it was using the equals method as above. Retrospectively however I think this isn't exactly clean. But anyway, that's a different topic.
            Do you have the exact same problem?
            Yes, I have the exact same problem. I have domain object and proxied domain object (through aop). The domain object can use equals() with a proxy, though inside equals() all attributes have to be accessed through getters, but the reverse never works. If i use proxied object equals() with a domain object, aop interceptor (in my case, cglib2AopProxy) will intercept equals() and return false! (since domain object is not a proxy)

            But thinking it over, does it make sense to compare a proxy with a domain object? Are they truly equal? (though i have to admit that business logic wise, they should be treated equal) But anyways, this will make proxy object no longer transparent to the caller. Since the caller has to know which one is proxy domain, and which one is no-proxied domain and then fire up equals().

            Comment


            • #7
              Hi,

              There is a problem invoking equals() method on CGLIB proxies that are created by Spring AOP.
              (the problem is not in CGLIB itself).

              The problem is, that all calls to equals() on a proxy are filtered by a special interceptor, which checks for equality of interfaces, advisors and targets, instead of just checking the targets for equality.
              (It invokes AopProxyUtils.equalsInProxy())

              This can be a problem, when you have the same Object wrapped by different proxies, and you want the application to identify this as the same Object.
              This happens for example, then the same Object is retrieved from a DB twice by two different calls.
              Assuming these two Objects are equals() , they end up being wrapped by different proxies because these are different calls.

              there are two workarounds:
              1. Make the equals() method final. This works, but has 2 problems:
                • You cannot reference any members inside the method because this refers to the proxy and not the target (you must use getId() instead of this.id ).
                • You can never override equals() on extending classes.
              2. Manually replace the callbacks in the CGLIB Enhancer after the proxy is created by Spring AOP.
              At the moment I fixed it by implementing option 2 (see code below).
              I override the Spring AOP EqualsInterceptor with my own.

              Code:
              package org.daniran.aop;
              
              import java.io.Serializable;
              import java.lang.reflect.Method;
              
              import net.sf.cglib.proxy.Callback;
              import net.sf.cglib.proxy.Factory;
              import net.sf.cglib.proxy.MethodProxy;
              
              import org.aopalliance.intercept.MethodInterceptor;
              import org.aopalliance.intercept.MethodInvocation;
              import org.springframework.aop.framework.Advised;
              import org.springframework.aop.framework.ProxyFactory;
              
              /**
               * Proxy utility
               * 
               * @author danny rankevich
               * 
               */
              public class ProxyUtils {
              
                  /**
                   * Dispatcher for the <code>equals</code> method, replaces the built in Spring AOP intercepter.
                   * <P>
                   * Checks only for target equality.
                   */
                  @SuppressWarnings("serial")
                  private static class EqualsInterceptor implements net.sf.cglib.proxy.MethodInterceptor, Serializable {
              
                      private final Object target;
              
                      public EqualsInterceptor(Object target) {
                          this.target = target;
                      }
              
                      @Override
                      public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
                          Object other = args[0];
                          if (proxy == other) {
                              return true;
                          }
                          if (other instanceof Factory) {
                              Callback callback = ((Factory) other).getCallback(INVOKE_EQUALS);
                              if (!(callback instanceof EqualsInterceptor)) {
                                  throw new RuntimeException("Error in Proxy Callbacks");
                              }
                              return this.target.equals(((EqualsInterceptor) callback).getTarget());
                          } else {
                              return this.target.equals(other);
                          }
                      }
              
                      protected Object getTarget() {
                          return target;
                      }
                  }
              
                  /**
                   * this is the fixed location of the equals callback
                   */
                  private static final int INVOKE_EQUALS = 5;
              
                  /**
                   * This method wraps an entity
                   * with a proxy object.
                   * 
                   * @param entity
                   * @return
                   */
                  public static Object wrapEntity(Object entity) {
                      ProxyFactory pf = new ProxyFactory();
                      pf.setTargetClass(entity.getClass());
                      pf.setTarget(entity);
                      pf.addAdvice(new MethodInterceptor() {
              
                          @Override
                          public Object invoke(MethodInvocation invocation) throws Throwable {
                              // do something with the method invocation
                              return invocation.proceed();
                          }
                      });
              
                      return patchProxy(pf.getProxy());
                  }
              
                  /**
                   * this is a patch for the CGLIB AOP Proxy
                   * 
                   * @param proxy
                   * @return the patched proxy
                   */
                  private static Object patchProxy(Object proxy) {
                      if (!(proxy instanceof Factory) || !(proxy instanceof Advised)) {
                          throw new RuntimeException("Cannot patch AOP Proxy");
                      }
                      Factory proxyFactory;
                      try {
                          proxyFactory = (Factory) proxy;
                          Advised advised = (Advised) proxy;
                          final Object target = advised.getTargetSource().getTarget();
                          proxyFactory.setCallback(INVOKE_EQUALS, new EqualsInterceptor(target));
              
                      } catch (Exception e) {
                          throw new RuntimeException("Cannot patch AOP Proxy");
                      }
                      return proxyFactory;
                  }
              }
              Thanks,

              Danny
              Last edited by daniran; Nov 25th, 2010, 11:19 AM. Reason: typos

              Comment


              • #8
                Thanks, danny, (Your code lives on )

                I've seen other references in _your_ code to the same problem:

                Code:
                class AnotherInterceptor implements org.aopalliance.intercept.MethodInterceptor {
                  ...
                        @Override
                        public boolean equals(Object arg0) {
                            // we must return true here in order for the object equals to be called
                            // since AopProxyUtils.equalsInProxy() compares advise && object equals
                            // so if the advice will always be true then the object equals will be called
                            return true;
                        }
                }
                why is that?

                Also, shouldn't we handle hashCode() as well?

                TIA, asaf :-)

                Comment

                Working...
                X