Creating Proxies Dynamically Using CGLIB Library with examples

CGLIB is a bytecode generation framework,there are many such frameworks which do same job.But CGLIB is high-level framework that would dynamically change classes providing its proxy and substituting the functionality of some methods.

Even JDK dynamic proxies can do same,but there are some cons in JDK proxies.JDK proxies will only work if your class is implemented from interface/s.Where as CGLIB does not have such restriction.It will create subclass(proxy) to any class.

CGLIB is a powerful, high performance code generation library, that relies on low-level
ASM framework.It is widely used behind the scenes in proxy-based Aspect Oriented Programming (AOP) frameworks, such as Spring AOP and dynaop, to provide method interceptions. Hibernate, the most popular object-relational mapping tool, also uses the CGLIB library to proxy single-ended (many-to-one and one-to-one) associations (not lazy fetching for collections, which is implemented using a different mechanism). EasyMock and jMock are libraries for testing Java code using mock objects. Both of them use the CGLIB library to create mock objects for classes that do not have interfaces.


Here I will explain creating proxies in four ways using CGLIB library.Let me take a simple java class and create proxy for this class.
public class JobsManager {
private static List<String> jobs = new ArrayList<String>();
      static {
            jobs.add("job1");
            jobs.add("job2");
            jobs.add("job3");
      }
       public List<String> getAllJobs() {
            return jobs;
      }
      public int getCount()
            return jobs.size();
      }
       public boolean createJob(String name) {
            return jobs.add(name);
      }
}
In all below example,we will create proxy for this JobsManager and do method calls on proxy instance.

1) Create proxy using callbackhandler InvocationHandler : 

public class JobsInvocationHandlerDemo {
    public static void main(final String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(JobsManager.class);
        enhancer.setCallback(new InvocationHandler() {
            @Override
            public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
                // redirecting all object class method calls to its super
                if (method.getDeclaringClass().equals(Object.class)) {
                    return NoOp.INSTANCE;
                }
                // returning static jobs list
                if (method.getName().equals("getAllJobs")) {
                    return Arrays.asList(new String[]{"job5", "job6", "job7"});
                }
                // returning static jobs count
                if (method.getName().equals("getCount")) {
                    return 555;
                }
                // which will create end less loop, you should be more care full in using invocation handler.
                //if(method.getName().equals("createJob"))
                //return method.invoke(obj,args);
                return null;
            }
        });
        // jobsManager is proxified class object
        JobsManager jobsManager = (JobsManager) enhancer.create();
        // jobs list will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getAllJobs().get(0), jobsManager.getAllJobs().get(0));
        // jobs count will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getCount(), jobsManager.getCount());
         //which will go end less loop if you uncomment above method.invoke
        //Assert.assertEquals(true, jobsManager.createJob("job4"));
    }
}
All the JobsManager proxy method calls will go though Invocationhanler, here you can write your own code or redirect.
But there is a limitation in Invocationhanler,if any method call dispatchs to its super class then it will result in an endless loop.

2) Create proxy using callbackhandler MethodInterceptor :

public class JobsMethodInterceptorDemo {
public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(JobsManager.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // redirecting all object class method calls to its super
                if (method.getDeclaringClass().equals(Object.class)) {
                    return NoOp.INSTANCE;
                }
                // returning some static jobs list
                if (method.getName().equals("getAllJobs")) {
                    return Arrays.asList(new String[]{"job5", "job6", "job7"});
                }
                // returning some static jobs count
                if (method.getName().equals("getCount")) {
                    return 555;
                }
                // redirecting it its super class
                if (method.getName().equals("createJob")) {
                    return methodProxy.invokeSuper(obj, args);
                }
                // NoOp Instance
                return NoOp.INSTANCE;
            }
        });
        // jobsManager is proxified class object
        JobsManager jobsManager = (JobsManager) enhancer.create();
        // jobs list will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getAllJobs().get(0), jobsManager.getAllJobs().get(0));
        // jobs count will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getCount(), jobsManager.getCount());
        // here both should be true.
        Assert.assertEquals(true, jobsManager.createJob("job4"));
    }
}
The MethodInterceptor allows full control over the intercepted method and offers some utilities for calling the method of the enhanced class.MethodInterceptor is the callback for all methods of a proxy, method invocations on the proxy are routed to this method before invoking the methods on the original object.

3)Create proxy using callbackhandler MethodInterceptor and CallBackFilter :

public class JobsInterceptorWithCallBackFilter {
    public static void main(final String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(JobsManager.class);
        enhancer.setCallbackFilter(new CallbackFilter() {
            @Override
            public int accept(Method method) {
                if (method.getDeclaringClass().equals(Object.class)) {
                    return 0;
                }
                if (method.getName().equals("getAllJobs")) {
                    return 1;
                }
                if (method.getName().equals("getCount") || method.getName().equals("createJob")) {
                    return 2;
                }
                return 0;
            }
        });
        enhancer.setCallbacks(new Callback[]{NoOp.INSTANCE, new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return Arrays.asList(new String[]{"job5", "job6", "job7"});
            }
        }, new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                return methodProxy.invokeSuper(obj, args);
            }
        }});
        // jobsManager is proxified class object
        JobsManager jobsManager = (JobsManager) enhancer.create();
        // jobs list will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getAllJobs().get(0), jobsManager.getAllJobs().get(0));
        // jobs count will be equal with proxy method call
        Assert.assertEquals(new JobsManager().getCount(), jobsManager.getCount());
        // here both should be true.
        Assert.assertEquals(true, jobsManager.createJob("job4"));
    }
}
If you want to use multiple callback filters based on some condition then use CallbackFilter. CallbackFilter is selectively apply callbacks on the methods. This feature is not available in the JDK dynamic proxy.
4) Create proxy using callbackhandler MethodInterceptor and CallBackHelper : 

public class JobsInterceptorWithCallBackHelper {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(JobsManager.class);
        CallbackHelper callbackHelper = new CallbackHelper(JobsManager.class, new Class[0]) {
            @Override
            protected Object getCallback(Method method) {
                if (method.getDeclaringClass().equals(Object.class)) {
                    return NoOp.INSTANCE;
                }
                if (method.getName().equals("getAllJobs")) {
                    return new FixedValue() {
                        @Override
                        public Object loadObject() throws Exception {
                            return Arrays.asList(new String[]{"job5", "job6", "job7"});
                        }
                    };
                }
                if (method.getName().equals("getCount") || method.getName().equals("createJob")) {
                    return new MethodInterceptor() {
                        @Override
                        public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                            return methodProxy.invokeSuper(obj, args);
                        }
                    };
                }
                return NoOp.INSTANCE;
            }
        };
        enhancer.setCallbackFilter(callbackHelper);
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        // jobsManager is proxified class object
        JobsManager jobsManager = (JobsManager) enhancer.create();
        // jobs list will not equal with proxy method call
        Assert.assertNotEquals(new JobsManager().getAllJobs().get(0), jobsManager.getAllJobs().get(0));
        // jobs count will be equal with proxy method call
        Assert.assertEquals(new JobsManager().getCount(), jobsManager.getCount());
        // here both should be true.
        Assert.assertEquals(true, jobsManager.createJob("job4"));
    }
}

CallBackHelper is one of the implementation of CallBackFilter provided by CGLIB.

There are other callbacks which are introduced for simplicity and performance :


  • FixedValue : It is useful to force a particular method to return a fixed value for performance reasons.
  • NoOp  : It delegates method invocations directly to the default implementations in the super class.
  • LazyLoader : It is useful when the real object needs to be lazily loaded. Once the real object is loaded, it is used for every future method call to the proxy instance.
  • Dispatcher : It has the same signatures as LazyLoader, but the loadObject method is always called when a proxy method is invoked.
  • ProxyRefDispatcher : It is the same as Dispatcher, but it allows the proxy object to be passed in as an argument of the loadObject method.


You can download source @ GitHub.



Show Comments: OR

0 comments: