1. Overview
In this article, we will be looking at the cglib (Code Generation Library) library. It is a byte instrumentation library used in many Java frameworks such as Hibernate or Spring. The bytecode instrumentation allows manipulating or creating classes after the compilation phase of a program.
2. Maven Dependency
To use cglib in your project, just add a Maven dependency (latest version can be found here):
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
3. Cglib
Classes in Java are loaded dynamically at runtime. Cglib is using this feature of Java language to make it possible to add new classes to an already running Java program.
Hibernate uses cglib for generation of dynamic proxies. For example, it will not return full object stored in a database but it will return an instrumented version of stored class that lazily loads values from the database on demand.
Popular mocking frameworks, like Mockito, use cglib for mocking methods. The mock is an instrumented class where methods are replaced by empty implementations.
We will be looking at the most useful constructs from cglib.
4. Implementing Proxy Using cglib
Let’s say that we have a PersonService class that has two methods:
public class PersonService { public String sayHello(String name) { return "Hello " + name; } public Integer lengthOfName(String name) { return name.length(); } }
Notice that first method returns String and the second one Integer.
4.1. Returning the Same Value
We want to create a simple proxy class that will intercept a call to a sayHello() method. The Enhancer class allows us to create a proxy by dynamically extending a PersonService class by using a setSuperclass() method from the Enhancer class:
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((FixedValue) () -> "Hello Tom!"); PersonService proxy = (PersonService) enhancer.create(); String res = proxy.sayHello(null); assertEquals("Hello Tom!", res);
The FixedValue is a callback interface that simply returns the value from the proxied method. Executing sayHello() method on a proxy returned a value specified in a proxy method.
4.2. Returning Value Depending on a Method Signature
The first version of our proxy has some drawbacks because we are not able to decide which method a proxy should intercept, and which method should be invoked from a superclass. We can use a MethodInterceptor interface to intercept all calls to the proxy and decide if want to make a specific call or execute a method from a superclass:
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersonService.class); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) { return "Hello Tom!"; } else { return proxy.invokeSuper(obj, args); } }); PersonService proxy = (PersonService) enhancer.create(); assertEquals("Hello Tom!", proxy.sayHello(null)); int lengthOfName = proxy.lengthOfName("Mary"); assertEquals(4, lengthOfName);
In this example, we are intercepting all calls when method signature is not from the Object class, meaning that i.e. toString() or hashCode() methods will not be intercepted. Besides that, we are intercepting only methods from a PersonService that returns a String. Call to a lengthOfName() method will not be intercepted because its return type is an Integer.
5. Bean Creator
Another useful construct from the cglib is a BeanGenerator class. It allows us to dynamically create beans and to add fields together with setter and getter methods. It can be used by code generation tools to generate simple POJO objects:
BeanGenerator beanGenerator = new BeanGenerator(); beanGenerator.addProperty("name", String.class); Object myBean = beanGenerator.create(); Method setter = myBean.getClass().getMethod("setName", String.class); setter.invoke(myBean, "some string value set by a cglib"); Method getter = myBean.getClass().getMethod("getName"); assertEquals("some string value set by a cglib", getter.invoke(myBean));
6. Creating Mixin
A mixin is a construct that allows combining multiple objects into one. We can include a behavior of a couple of classes and expose that behavior as a single class or interface. The cglib Mixins allow the combination of several objects into a single object. However, in order to do so all objects that are included within a mixin must be backed by interfaces.
Let’s say that we want to create a mixin of two interfaces. We need to define both interfaces and their implementations:
public interface Interface1 { String first(); } public interface Interface2 { String second(); } public class Class1 implements Interface1 { @Override public String first() { return "first behaviour"; } } public class Class2 implements Interface2 { @Override public String second() { return "second behaviour"; } }
To compose implementations of Interface1 and Interface2 we need to create an interface that extends both of them:
public interface MixinInterface extends Interface1, Interface2 { }
By using a create() method from the Mixin class we can include behaviors of Class1 and Class2 into a MixinInterface:
Mixin mixin = Mixin.create( new Class[]{ Interface1.class, Interface2.class, MixinInterface.class }, new Object[]{ new Class1(), new Class2() } ); MixinInterface mixinDelegate = (MixinInterface) mixin; assertEquals("first behaviour", mixinDelegate.first()); assertEquals("second behaviour", mixinDelegate.second());
Calling methods on the mixinDelegate will invoke implementations from Class1 and Class2.
7. Conclusion
In this article, we were looking at the cglib and its most useful constructs. We created a proxy using an Enhancer class. We used a BeanCreator and finally, we created a Mixin that included behaviors of other classes.
Cglib is used extensively by the Spring framework. One example of using a cglib proxy by Spring is adding security constraints to method calls. Instead of calling a method directly, Spring security will first check (via proxy) if a specified security check passes and delegate to the actual method only if this verification was successful. In this article, we saw how to create such proxy for our own purpose.
The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.