Java Byte Code Modification Using Cglib and Asm

This article looks at how cglib library can be used for java byte code manipulation at runtime. A number of popular java projects such as spring framework, guice and hibernate internally use cglib for byte code manipulation.

Byte code modification enables manipulation or creation of new classes after compilation phase of java applications. In java, classes are linked dynamically at runtime and hence it is possible to switch the compile dependency with a different implementation. For example, spring framework uses byte code manipulation to return a modified version of the original class at runtime. This allows spring framework add new features to existing classes. For example, spring AOP heavily uses this feature.

An interesting example of this behavior is java configuration classes in spring. When we write a java configuration class for beans (@Configuration and @Bean), we create new instances of the bean. However at runtime spring replaces this class with an implementation that internally reuses an existing instance for a bean (making it a singleton). Spring uses cglib (which internally uses low level asm library) for byte code manipulation. The following sections contain a number of example of byte code modification using cglib.

This example uses the following tools,

Create a simple gradle java project using eclipse. Modify the build.gradle to add the dependency on cglib 3.2.5,

apply plugin: 'application'

repositories {
    jcenter()
}

dependencies {
    compile 'cglib:cglib:3.2.5'
}

Using Cglib Enhancer Class

The cglib Enhancer class can be used to create dynamic subclasses for method interception. Note that all methods of the class are intercepted. A derived instance of Callback interface can be used handle method interception. For example, the derived interface FixedValue can be used return a fixed value instead of invoking the method of the original class. The following class demonstrates the use of Enhancer and Fixedvalue,

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;

public class EnhancerFixedValueDemo {

    public static void main(String[] args) {

        Enhancer er = new Enhancer();
        er.setSuperclass(SimpleClass.class);
        er.setCallback(new FixedValue() {

            @Override
            public Object loadObject() throws Exception {
                // This is called for every method call of the original class
                // We return a fixed value of the same type as the original
                // method
                return "Hello FixedValue!";
            }

        });

        SimpleClass sc = (SimpleClass) er.create();
        // We think we are using Simple Class!
        // But instead Hello FixedValue! is printed
        System.out.println(sc.getHello());

    }

}

class SimpleClass {
    public String getHello() {
        return "Hello World!";
    }
}

We can use the MethodInterceptor interface to invoke the original method call and optionally return a modified version. Check out the following example which uses Enhancer and MethodInterceptor,

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 EnhancerMethodInterceptorDemo {
    public static void main(String[] args) {

        Enhancer er = new Enhancer();
        er.setSuperclass(SimpleClass2.class);
        er.setCallback(new MethodInterceptor() {

            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                // Call the method of the original class
                Object o = proxy.invokeSuper(obj, args);

                // Take a look at what original class returned
                System.out.println("Inspecting " + o);

                // We can even modify it if needed.
                // if you have mutliple methods, return type should be checked
                // (use method.getReturnType())
                return ((String) o).toUpperCase();
            }
        });

        SimpleClass2 sc = (SimpleClass2) er.create();
        System.out.println(sc.getHello());

    }

}

class SimpleClass2 {
    public String getHello() {
        return "Hello World!";
    }
}

Using Cglib ImmutableBean

The ImmutableBean in cglib can be used to return a read only copy of a java bean. Any modification attempted will throw a runtime error. Check out the following example,

import net.sf.cglib.beans.ImmutableBean;

public class ImmutableBeanDemo {
    public static void main(String[] args) {
        SimpleBean bean = new SimpleBean();
        bean.setName("QPT");

        SimpleBean bean2 = (SimpleBean) ImmutableBean.create(bean);

        // This throws a runtime exception
        // java.lang.IllegalStateException
        bean2.setName("QPT2");

    }

}

class SimpleBean {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Using Cglib BeanCopier

The BeanCopier in cglib can be used to copy properties from one bean class to another bean class. The copying is done looking at the matching property name. Check out the following example,

import net.sf.cglib.beans.BeanCopier;

public class BeanCopierDemo {
    public static void main(String[] args) {
        SimpleBeanA beanA = new SimpleBeanA();
        beanA.setName("QPT");

        SimpleBeanB beanB = new SimpleBeanB();

        BeanCopier copier = BeanCopier.create(SimpleBeanA.class, SimpleBeanB.class, false);
        copier.copy(beanA, beanB, null);

        System.out.println(beanB.getName());
    }

}

class SimpleBeanA {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

class SimpleBeanB {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Do you have a programming problem that you are unable to solve? Please send us your problem and we will publish the solution! Please email us.

Leave a Reply