Introduction

The proxy pattern has many application scenarios in Java, and there are static code and dynamic proxies. Static proxies are implemented by weaving in code at writing, compiling or loading time, while dynamic proxies are implemented at runtime. In simple terms, static proxies exist before runtime, while dynamic proxies exist at runtime. And there are two common implementations of dynamic proxies.

  • JDK Proxy: JDK Proxy comes with the JDK and does not require the introduction of external libraries and is proxied by implementing interfaces.
  • CGLib: CGLib implements bytecode generation by means of ASM techniques. Proxy by way of inheritance.

Now let’s show each of the two approaches in code.

JDK Proxy

The JDK Proxy is implemented as a proxy by implementing an interface. We start by defining an interface.

1
2
3
public interface Flyable {
    String fly(String route);
}

Then there is an implementation class.

1
2
3
4
5
6
7
public class Bird implements Flyable {
    @Override
    public String fly(String route) {
        System.out.println("Route: " + route);
        return route;
    }
}

We then need to define an InvocationHandler to change the logic of the method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class FlyableInvocation  implements InvocationHandler {
    private final Flyable target;

    public FlyableInvocation(Flyable target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.nanoTime();
        System.out.println(target + ": ===JDK proxy===");
        Object result = method.invoke(this.target, args);
        System.out.println(target + ": ===JDK proxy===");
        long end = System.nanoTime();
        System.out.println("Executing time: " + (end - start) + " ns");
        return result;
    }
}

Here we have added logging before and after the method call and also calculated the execution time of the method.

The complete call is as follows.

1
2
3
4
5
6
7
8
9
public class JDKDynamicProxy {
    public static void main(String[] args) {
        ClassLoader classLoader = JDKDynamicProxy.class.getClassLoader();
        Class<?>[] interfaces = Bird.class.getInterfaces();
        Bird bird = new Bird();
        Flyable flyable = (Flyable) Proxy.newProxyInstance(classLoader, interfaces, new FlyableInvocation(bird));
        flyable.fly("Go to pkslow.com");
    }
}

The Proxy.newProxyInstance method generates an instance of the proxy and executes the methods of this instance, while the original instance bird is proxied.

The program runs as follows.

1
2
3
4
com.pkslow.basic.jdk.Bird@3551a94: ===JDK proxy===
Route: Go to pkslow.com
com.pkslow.basic.jdk.Bird@3551a94: ===JDK proxy===
Executing time: 18195736 ns

Viewing proxy classes

With the VM parameter, we can also view the generated proxy classes.

1
2
3
4
# JDK 8
-Dsum.misc.ProxyGenerator.saveGeneratedFiles=true
# JDK 11
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

Of course, this can also be achieved by setting system properties in the Java code.

Once this is done, run the program again and the .class file for the proxy class will be generated.

Proxy classes generated by jdk

CGLib

CGLib is proxied by inheritance, so let’s start by defining a class.

1
2
3
4
5
6
public class Animal {
    public String talk(String str) {
        System.out.println("Talking: " + str);
        return str;
    }
}

Then define an Interceptor, a class whose role is to generate proxy instances and define how to change the execution of the target method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CGLibProxy<T> implements MethodInterceptor {
    private T target;

    public T getInstance(T target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        long start = System.nanoTime();
        System.out.println(target + ": ===CGLib proxy===");
        Object result = methodProxy.invoke(this.target, args);
        System.out.println(target + ": ===CGLib proxy===");
        long end = System.nanoTime();
        System.out.println("Executing time: " + (end - start) + " ns");

        return result;
    }
}

Here again, logging is added before and after the method call and the execution time is recorded.

The complete execution code is as follows.

1
2
3
4
5
6
7
8
public class CGLibDynamicProxy {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/larry/IdeaProjects/pkslow-samples/java-basic/jdk-cglib-proxy/target/cglib_proxy_classes");
        CGLibProxy<Animal> cgLibProxy = new CGLibProxy<>();
        Animal animal = cgLibProxy.getInstance(new Animal());
        animal.talk("Hi, pkslow");
    }
}

The system property is set here to export the generated proxy classes to a .class file for easy learning.

The results of the run are as follows.

1
2
3
4
com.pkslow.basic.cglib.Animal@57855c9a: ===CGLib proxy===
Talking: Hi, pkslow
com.pkslow.basic.cglib.Animal@57855c9a: ===CGLib proxy===
Executing time: 28396871 ns

Summary

JDK Proxy essentially uses the mechanism of reflection, whereas CGLib uses ASM, so CGLib will perform better. However, neither of them support final classes and methods, as there is no way to change final methods through interfaces or inheritance.

Example code can be found here.

Reference: https://www.pkslow.com/archives/jdk-cglib-proxy