Sunday 29 May 2016

Understanding CGLIB


CGLIB is a byte code generation library which creates and link proxy classes at runtime.As java classes can be dynamically linked , we can add new classes in running java program.It simply creates a subclass of your class by reading its byte code. Under the hood it uses ASM which is a byte code manipulation framework. ASM helps CGLIB to generate java byte code at runtime.

Frameworks like spring, hibernate, mockito uses CGLIB. Spring AOP uses proxy-based Aspect Oriented Programming which has a feature of method interception.Hibernate implemented a feature of returning proxy of an object of entity graph from database where child entities are fetched from database when required.Hibernate uses CGLIB for this feature.

Lets start with some coding examples. CGLIB has a class named Enhancer which helps us to create proxy class for all the classes implementing or not implementing any interface. It is quite similar to Proxy class in java.


       
package com.akash.mycglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class FirstCGLIBProgram {

    public static void main(String[] args) throws Exception {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(DemoClass.class);

        // here we have to specify object of a class that implements Callback interface
        enhancer.setCallback(new MethodInterceptorProxy());

        // Generate proxy class and its object and uses the no-arg constructor of the superclass
        DemoClass proxy = (DemoClass) enhancer.create();

        System.out.println(proxy.test1(null));
        System.out.println("------------");
        System.out.println(proxy.test2(null));
        System.out.println("------------");
        System.out.println(proxy.test3(null));
        System.out.println("------------");
        System.out.println(proxy.test5(null));
        System.out.println("------------");
    }
}


       
class MethodInterceptorProxy implements MethodInterceptor {

    public Object intercept(Object obj, Method method, Object[] args, 
                        MethodProxy proxy) throws Throwable {

        System.out.println("intercepting method here...");
        System.out.println("proxy :"+proxy.getClass().getName());
        System.out.println("obj :"+obj.getClass().getName());

        if (method.getDeclaringClass() != Object.class && 
                        method.getReturnType() == String.class) {

            proxy.invokeSuper(obj, args);
            return "Method Interceptor CGLIB";

        } else {
            return proxy.invokeSuper(obj, args);
        }
    }
}


       
class DemoClass {

    //public methods are proxied
    public String test1(String input) {
        System.out.println("public method");
        return "test-1";
    }

    //protected methods are proxied
    protected String test2(String input) {
        System.out.println("protected method");
        return "test-2";
    }

    //default methods are proxied
    String test3(String input) {
        System.out.println("default method");
        return "test-3";
    }
    
    //private methods are not proxied
    private String test4(String input) {
        System.out.println("private method");
        return "test-4";
    }

    //static methods are not proxied
    public static String test5(String input) {
        System.out.println("static method");
        return "test-5";
    }
}


output:
       
intercepting method here...
proxy :net.sf.cglib.proxy.MethodProxy
obj :com.akash.mycglib.DemoClass$$EnhancerByCGLIB$$9c242b46
public method
Method Interceptor CGLIB
------------
intercepting method here...
proxy :net.sf.cglib.proxy.MethodProxy
obj :com.akash.mycglib.DemoClass$$EnhancerByCGLIB$$9c242b46
protected method
Method Interceptor CGLIB
------------
intercepting method here...
proxy :net.sf.cglib.proxy.MethodProxy
obj :com.akash.mycglib.DemoClass$$EnhancerByCGLIB$$9c242b46
default method
Method Interceptor CGLIB
------------
static method
test-5
------------


To understand above working, lets summarize some rules regarding CGLIB :

  • private , static and final methods are not proxied.
  • The class generated by cglib will be in same package as the proxied class and hence cglib can override default methods.
  • final classes are not proxied.
  • All classes generate by cglib are stored in a special section of memory called Metaspace.
  • Only those methods are proxied that are invokeVirtual. 
  • invokeSpecial and invokeStatic methods are not proxied.
  • static methods are invokeStatic.
  • constructors , methods called using super keyword and private methods are invokeSpecial.

There are basically 3 types of methods at bytecode level in java
  • invokespecial
  • invokestatic
  • invokevirtual

NOTE : Only invokevirtual methods are proxied by cglib.

you can determine these method types by reading byte code by using javap tool:
javap -c -private FirstCGLIBProgram.class

output :
       
Compiled from "FirstCGLIBProgram.java"
public class com.akash.mycglib.FirstCGLIBProgram {
  public com.akash.mycglib.FirstCGLIBProgram();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: new           #2                  // class net/sf/cglib/proxy/Enhancer
       3: dup
       4: invokespecial #3                  // Method net/sf/cglib/proxy/Enhancer."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #4                  // class com/akash/mycglib/DemoClass
      11: invokevirtual #5                  // Method net/sf/cglib/proxy/Enhancer.setSuperclass:(Ljava/lang/Class;)V
      14: aload_1
      15: new           #6                  // class com/akash/mycglib/MethodInterceptorProxy
      18: dup
      19: invokespecial #7                  // Method com/akash/mycglib/MethodInterceptorProxy."<init>":()V
      22: invokevirtual #8                  // Method net/sf/cglib/proxy/Enhancer.setCallback:(Lnet/sf/cglib/proxy/Callback;)V
      25: aload_1
      26: invokevirtual #9                  // Method net/sf/cglib/proxy/Enhancer.create:()Ljava/lang/Object;
      29: checkcast     #4                  // class com/akash/mycglib/DemoClass
      32: astore_2
      33: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      36: aload_2
      37: aconst_null
      38: invokevirtual #11                 // Method com/akash/mycglib/DemoClass.test1:(Ljava/lang/String;)Ljava/lang/String;
      41: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #13                 // String ------------
      49: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      55: aload_2
      56: aconst_null
      57: invokevirtual #14                 // Method com/akash/mycglib/DemoClass.test2:(Ljava/lang/String;)Ljava/lang/String;
      60: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      63: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      66: ldc           #13                 // String ------------
      68: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      71: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      74: aload_2
      75: aconst_null
      76: invokevirtual #15                 // Method com/akash/mycglib/DemoClass.test3:(Ljava/lang/String;)Ljava/lang/String;
      79: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      82: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      85: ldc           #13                 // String ------------
      87: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      90: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      93: aload_2
      94: pop
      95: aconst_null
      96: invokestatic  #16                 // Method com/akash/mycglib/DemoClass.test5:(Ljava/lang/String;)Ljava/lang/String;
      99: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     102: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
     105: ldc           #13                 // String ------------
     107: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     110: return
}



NOTESee lines 38,57,76,96  of the output from javap tool. This shows that test1() , test2() and test3() are proxied as they are invokevirtual. test5() method is not proxied as it is a invokestatic method.


There are few more child interfaces of Callback interface that we should know while using CGLIB :
  • MethodInterceptor : All generated proxied methods call this method instead of the original method.The original method may either be invoked by normal reflection using the Method object,or by using the MethodProxy (faster).
  • NoOp : Methods using this callback will delegate directly to the default (super) implementation in the base class.
  • LazyLoader : Called as soon as the first lazily-loaded method in the enhanced instance is invoked. The same object is then used for every future method call to the proxy instance.
  • Dispatcher : This is similar to LazyLoader but it always evaluates the returning value.
  • InvocationHandler : This callback type is primarily for use by the Proxy class but may be used with Enhancer as well.
  • FixedValue : callback that simply returns the value to return from the proxied method. No information about what method is being called is available to the callback, and the type of the returned object must be compatible with the return type of the proxied method.

In the end we must be aware of Object class methods while creating proxies.As final methods are not proxied, Object#wait()  Object#notify() and Object#notifyAll() are not proxied.  Object#finalize() and Object#clone() should never be intercepted while creating a proxy class. Garbage collector handles proxied Object#finalize() method differently.Also if any hard reference are taken in proxied method , then it may result in some serious problems in case of Object#finalize() method.



Thanks

3 comments:

  1. java proxy fundamentals, well explained

    ReplyDelete
  2. All proxy concepts in java in one blog, well done

    ReplyDelete
  3. Harrah's Cherokee Casino Resort debuts COVID-19 vaccination
    Harrah's Cherokee Casino 충청남도 출장샵 Resort has rolled out COVID-19 vaccination 순천 출장샵 options 광주 출장마사지 into 통영 출장마사지 its casino 오산 출장안마 floors this week. The hotel's COVID-19 vaccination

    ReplyDelete