This post identifies the differences between them and these differences could give us the answer why the CGLib Proxy Enhancer is a good substitution for the Java Dynamic Proxy.
Java Dynamic Proxy
diagram 1.0 Class diagram |
Java Dynamic Proxy (java.lang.reflect.Proxy) creates a proxy instance that implements the Interfaces which are given during runtime. A proxy instance is associated with a single InvocationHandler instance and this InvocationHandler instance has to keep the proxied instances so that their real method could be invoked.
diagram 1.1: Method invocation flow |
Every method invocation made to the proxy instance will be encoded and dispatched to the associated InvocationHandler's invoke() method and this is the place where we could intercept or add any additional functionality before we decide to invoke the real method of the proxied instance.
The code implementation below realizes the class diagram in diagram 1.0.
The code implementation below realizes the class diagram in diagram 1.0.
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JavaDynamicProxyApp { public static void main(String[] args) { Person person = new Person("HauChee"); Animal animal = new Animal(); Object proxy = Proxy.newProxyInstance( MyInvocationHandler.class.getClassLoader(), new Class[] {IPerson.class, IAnimal.class}, new MyInvocationHandler(person, animal)); System.out.println(proxy instanceof Object); // output: true System.out.println(proxy instanceof IPerson); // output: true System.out.println(proxy instanceof IAnimal); // output: true /** * Proxy class implements the interfaces not the concrete class * therefore, any public method in Person such as think() will never * be invoked from proxy instance. */ System.out.println(proxy instanceof Person); // output: false System.out.println(proxy instanceof Animal); // output: false IPerson proxiedPerson = (IPerson) proxy; proxiedPerson.getName(); // output: Intercepted.. Person name.. proxiedPerson.eat(); // output: Intercepted.. Person eat.. IAnimal proxiedAnimal = (IAnimal) proxy; proxiedAnimal.eat(); // output: Intercepted.. Person eat.. /** * WAIT A MINUTE! ISN't IT SHOULD SHOW "Animal eat.." INSTEAD? * * Although eat() method is called based on IAnimal interface * but because there is a duplicate method eat() in IPerosn * therefore Method object passed into MyInvocationHandler.invoke() * method always take from the foremost interface which is IPerson * in this case. */ } } class MyInvocationHandler implements InvocationHandler { private Object proxiedPerson; private Object proxiedAnimal; public MyInvocationHandler(Object person, Object animal) { this.proxiedPerson = person; this.proxiedAnimal = animal; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.print("Intercepted.. "); if (method.getDeclaringClass() == IPerson.class) { // Invoke real method of Person object return method.invoke(proxiedPerson, args); } // Invoke real method of Animal object return method.invoke(proxiedAnimal, args); } } class Person implements IPerson { private String name; Person(String name) { this.name = name; } @Override public String getName() { System.out.println("Person name.."); return name; } @Override public void eat() { System.out.println("Person eat.."); } public void think() { System.out.println("Person think.."); } } class Animal implements IAnimal { @Override public void eat() { System.out.println("Animal eat.."); } } interface IPerson { String getName(); void eat(); } interface IAnimal { void eat(); }
Characteristics of Java Dynamic Proxy
- Only the methods declared in the given interfaces and java.lang.Object's methods such as hashcode(), equals() and toString() are eligible to be intercepted, other public methods of the proxied class are ignored. This is because there is no way to call the other public method from the proxy instance. In diagram 1.0, think() method of Person is out of the proxy scope.
- Clear-cut between the proxy instance and the proxied instances. The InvocationHandler instance becomes the bridge between them. It is allowed to have more than 1 proxied object because after all it is up to the programmer to decide how and which real method of which proxied object to be invoked
- There is only one InvocationHandler instance associated with a proxy instance. Its only method invoke() is simple and raw. It is the programmer's job to extends the capabilities of the InvocationHandler.
CGLib Proxy Enhancer
CGLib Proxy Enhancer (net.sf.cglib.proxy.Enhancer) creates a proxy instance that extends a concrete class in addition to implements the interfaces. A proxy instance is associated with a list of Callback instances that could serve for the different purpose other than purely method interception. Enhancer uses CallbackFilter instance to determine which Callback instance to use when a proxy method is invoked.diagram 2.1: Method invocation flow |
Every method invocation made to the proxy instance will be encoded and dispatched to the associated Callback method according to the signal given by CallbackFilter instance. The Callback methods are the place where we could intercept or add additional functions to the real method. After that, we could decide to invoke the proxy's superclass's real method accordingly.
The code implementation below realizes the class diagram in diagram 2.0.
import java.lang.reflect.Method; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.FixedValue; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGLibProxyEnhancerApp { public static void main(String[] args) { Object proxy = Enhancer.create( Mutant.class, new Class[] {IPerson.class, IAnimal.class}, new MyClassbackFilter(), new Callback[] {new MyMethodInterceptor(), new MyFixedValue()}); System.out.println(proxy instanceof Object); // output: true System.out.println(proxy instanceof IPerson); // output: true System.out.println(proxy instanceof IAnimal); // output: true System.out.println(proxy instanceof Mutant); // output: true Mutant proxiedMutant = Mutant.class.cast(proxy); /** * Although setName() method is not declared in proxy interfaces * but it is eligible to be intercepted because proxy class is a subclass * of Mutant and inherit setName() method. */ proxiedMutant.setName("HauChee");// output: Intercepted..Mutant set name.. // output: Intercepted and always return "Fixed" System.out.println(proxiedMutant.getName()); // output: Intercepted and always return "Fixed" System.out.println(proxiedMutant.toString()); proxiedMutant.eat(); // output: Intercepted..Mutant eat.. } } class MyClassbackFilter implements CallbackFilter { @Override public int accept(Method method) { if (method.getReturnType() == String.class) { return 1; } return 0; } } class MyFixedValue implements FixedValue { @Override public Object loadObject() throws Exception { return "Intercepted and always return \"Fixed\""; } } class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy mp) throws Throwable { System.out.print("Intercepted.."); // Invoke the real method of proxy's superclass return mp.invokeSuper(proxy, args); } } class Mutant implements IPerson, IAnimal { private String name; /** * Enhancer requires the no-arg constructor to construct the proxy instance. * IllegalArgumentException is thrown if Enhancer can't find the no-arg * constructor during proxy creation time. */ public Mutant() { } public Mutant(String name) { this.name = name; } @Override public String getName() { System.out.println("Mutant get name.."); return name; } @Override public void eat() { System.out.println("Mutant eat.."); } public void setName(String name) { System.out.println("Mutant set name.."); this.name = name; } } interface IPerson { String getName(); void eat(); } interface IAnimal { void eat(); }
Characteristics of CGLib Proxy Enhancer
- Since the proxy class is the subclass of concrete class and interfaces, all its non-final methods which are visible to the Enhancer are intercept-able. In fact, the interfaces are optional. The proxy class could live solely as the subclass of a concrete class without implement any interface. This is very useful when we want to proxy a class that not meant to implement any interface.
- There is no distinction between proxy instance and proxied instance. The proxy instance is treated as the proxied instance as it directly inherits all public behaviors from its concrete class. This has enforced one to one relationship between them. After all, this is what we are looking for compared to handling multiple proxied instances at once which could increase the complexity.
- CGLib has extended the method interception capability by providing a few of Callbacks to serve different purpose and CallbackFilter to control the Callbacks. Programmer could focus on the functionality implementation and has better control over the method interception.
Summary
Table below summarise the differences between Java Dynamic Proxy and CGLib Proxy Enhancer.In short, CGLib Proxy Enhancer provides better framework and greater capability compare to Java Dynamic Proxy.
References:
http://cglib.sourceforge.net/apidocs/net/sf/cglib/Enhancer.html
http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html
http://mydailyjava.blogspot.my/2013/11/cglib-missing-manual.html