A lot of frameworks create proxies to apply aspect oriented programming or the lazy loading pattern.
Hibernate for example uses cglib or javasissit to create lazy loading proxies. The famous springframework mainly uses java proxyies created via java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) or cglib as well.
Therefore it is important to know exactly how proxies work and the impact of proxies on your implementations. Especially when implementing basic methods like equals() and hashCode().
How proxies work
A proxy is (like the name implies) not the real object. It is another object with the same interface as the real object. Since it has the same interface it can either delegate all calls to the real object or it can do something before and/or after the delegate call. Thats why a proxy is mostly used when implementing AOP (aspect oriented programming), because aspects are code fragments that must be executed before and/or after a method call. It becomes more clear if you take a look at the pointcut expression language that determines where aspects are applied. Take a closer look at http://www.eclipse.org/aspectj/ for details.
Java, CGlib or Javassist Proxies
Java Proxies | CGlib or Javassist Proxies |
---|---|
Java proxies are created via java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler). They are based only upon interfaces. Creating proxies for classes is not allowed yet. | CGlib and javassist are bytecode manipulation libraries. Therefore they can create proxies for a classes and interfaces. Proxies for classes are created by subclassing the target object’s class to apply the proxy pattern. |
As you can see the JavaProxy implements the same interface as the TargetObject (the object that is proxied). Therfore you can pass references of the JavaProxy to any object that needs an instance of the proxied interface. The JavaProxy uses an InvocationHandler that handles calls to the proxies interface. The InvocationHandler might create the TargetObejct on demand. In this case the InvocationHandler implements the lazy loading pattern. | A class based proxy extends the TargetObject and can therfore be used whereever a TargetObject is needed, because it is one. This also explains why final classes and methods can not be proxied in this way, because you can not extend a final class and you can not override a final method. There are frameworks like powermock that seem that they can do this, but they do it with a trick. They manipulate the bytecode by removing the final modifiers when the class is loaded. See PowerMockClassVisitor.java for details. I will stop here because these issues are out of scope of this blog. |
Impact of proxies on equals and hashCode
Now that we have a model of how proxies work we can think about basic java object methods like equals() and hashCode(). A proxy is also a java object and therefore you can also call equals() and hashCode() on it. But the question is: which identity it should have? Is own or the identity of the target object? So a proxy must decide if he handles equals() and hashCode() by itself or if it delegates the call to the equals() and hashCode() methods of it’s target object.
Let us take a look at an example that shows how the equals() and hashCode() methods are commonly implemented.
public class Person { private String firstname; private String lastname; public Person(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } protected Person() { } public String getFirstname() { return firstname; } public String getLastname() { return lastname; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((firstname == null) ? 0 : firstname.hashCode()); result = prime * result + ((lastname == null) ? 0 : lastname.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (firstname == null) { if (other.firstname != null) return false; } else if (!firstname.equals(other.firstname)) return false; if (lastname == null) { if (other.lastname != null) return false; } else if (!lastname.equals(other.lastname)) return false; return true; } }
The example Person class above looks normal, but can you see the problematic instructions in the source code in case of using a proxy?
Don’t be sad if you don’t see it. It’s hard to see the problem if you just look at some source code. The problem is that the equals() and hashCode() methods use the instance variables (firstname, lastname) directly. They do not use the accessor method getFirstname() or getLastname(). Normally this is not a problem, but in case of using proxies it becomes one. I will show you know a small unit test that calls equals on proxies, non proxies and vice versa. When you read that test you might ask yourself why the test should successfully. I can prove that it does and you can try it by your own. The complete demonstration code is available at Java Proxy Pitfalls Source.
@Test public void personEqualsTest() { Person person = new Person("René", "Link"); Person personProxy = createProxy(person); boolean isEqual = person.equals(person); Assert.assertTrue(isEqual); isEqual = person.equals(personProxy); Assert.assertFalse(isEqual); isEqual = personProxy.equals(personProxy); Assert.assertFalse(isEqual); isEqual = personProxy.equals(person); Assert.assertTrue(isEqual); }
Thie big question still is: Why does personProxy.equals(personProxy) or person.equals(personProxy) evaluate to false while personProxy.equals(person) evaluates to true?
It’s hard to see with just the class diagram above about cglib proxies in mind, but it gets more clear if we take a look at an object diagram.
The following object diagram is not uml specification compliant, because the tool http://yuml.me it was made with currently (only) supports class diagrams.
So let’s analyse the test code above
isEqual = personProxy.equals(personProxy); Assert.assertFalse(isEqual);
The code calls equals() on the personProxy object. The personProxy extends Person and it is also a Person. So it has all it’s instance variables (firstname, lastname). As mentioned above the cglib or javassist libraries create proxies by extending the proxied object’s class. This proxy class is the personProxy in the object diagram above. When the cglib library creates that proxy it just instantiates the class Person_CGLIB by using it’s default constructor. Of course it can not do anything else or it would have to know how to get the arguments for the constructor. All instance variables are null per defaul, because the default constructor is used. The call on the personProxy‘s equals method is delegated to the target object person:Person. The argument that is passed to the personProxy‘s equals() method is a personProxy. So also the argument passed to target object’s (person:Person) equals() method is the personProxy. The person:Person‘s equals method accesses the instance variables of the argument object directly and this instance variables are the variables of the personProxy. Remember that they are null, because it is the proxy. If the equals() method whould use the accessor methods getFirstname and getLastname instead that calls will be delegated by the proxy to the target object and therefore return the correct values.
This example shows how the usage of proxies can affect the implementation of methods. If you use the java collections framework the equals method is very important and you must take care about proxies.
Some people might argue that the InvocationHandler is implemented wrong. He could de-proxy an argument object before passing it to the target object. But this would also mean that if the target object invokes any method on the argument object the call will not go through the proxy. This might also be a problem because aspects can not be applied anymore.
If you use the accessor methods instead the Person‘s equals() method will not cause such problems.
public class Person { ... public String getFirstname() { return firstname; } public String getLastname() { return lastname; } ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (getFirstname()== null) { if (other.getFirstname()!= null) return false; } else if (!getFirstname().equals(other.getFirstname())) return false; if (getLastname()== null) { if (other.getLastname()!= null) return false; } else if (!getLastname().equals(other.getLastname())) return false; return true; } }