Backend Development 21 min read

Understanding Java Dynamic Proxy: Static vs Dynamic Proxy and Implementations with JDK, CGLIB, Javassist, and ASM

This article explains Java's proxy pattern, compares static and dynamic proxies, and provides detailed examples of JDK dynamic proxy, CGLIB, Javassist, and ASM implementations, highlighting their mechanisms, code samples, and practical considerations for backend development.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding Java Dynamic Proxy: Static vs Dynamic Proxy and Implementations with JDK, CGLIB, Javassist, and ASM

In Java, dynamic proxies are widely used for AOP, RPC, annotation processing, logging, global exception handling, and transaction management. The article first introduces the proxy pattern, a structural design pattern that allows an object to delegate operations to another object, enhancing functionality without modifying the original code.

An analogy compares a celebrity using an agent to illustrate static versus dynamic proxy concepts. Static proxy involves a manually created proxy class known at compile time, while dynamic proxy delegates method calls to a handler determined at runtime.

Static Proxy Example

The article defines a UserDao interface, an implementation UserDaoImpl , and a proxy class UserProxy that forwards calls to the real implementation. A test class demonstrates usage:

public interface UserDao {
    void saveUser();
}

public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("---- 保存用户 ----");
    }
}

public class UserProxy {
    private UserDao userDao;
    public UserProxy(UserDao userDao) { this.userDao = userDao; }
    public void saveUser() {
        System.out.println("---- 代理开始 ----");
        userDao.saveUser();
        System.out.println("---- 代理结束 ----");
    }
}

public class UserTest {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoImpl();
        UserProxy userProxy = new UserProxy(userDao);
        userProxy.saveUser();
    }
}

This approach is a static proxy because the proxy class is compiled ahead of time.

JDK Dynamic Proxy

A UserHandler class implements InvocationHandler to intercept method calls. The dynamic proxy is created with Proxy.newProxyInstance :

public class UserHandler implements InvocationHandler {
    private UserDao userDao;
    public UserHandler(UserDao userDao) { this.userDao = userDao; }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---- 开始插入 ----");
        Object result = method.invoke(userDao, args);
        System.out.println("---- 插入完成 ----");
        return result;
    }
}

public static void dynamicProxy() {
    UserDao userDao = new UserDaoImpl();
    InvocationHandler handler = new UserHandler(userDao);
    ClassLoader loader = userDao.getClass().getClassLoader();
    Class
[] interfaces = userDao.getClass().getInterfaces();
    UserDao proxy = (UserDao) Proxy.newProxyInstance(loader, interfaces, handler);
    proxy.saveUser();
}

JDK dynamic proxy works only for interfaces and relies on reflection.

Static vs Dynamic Proxy Comparison

Static proxy classes are generated or written by developers before compilation.

Dynamic proxy classes are generated at runtime using reflection.

Static proxies typically target a single class; dynamic proxies can handle all interfaces of a target.

Dynamic proxies require an InvocationHandler implementation.

CGLIB Dynamic Proxy

CGLIB creates a subclass of the target class and overrides its methods. Example Maven dependency and code:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

Define UserService and an interceptor:

public class UserService {
    public void saveUser() {
        System.out.println("---- 保存用户 ----");
    }
}

public class AutoMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("---- 方法拦截 ----");
        return methodProxy.invokeSuper(obj, args);
    }
}

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(UserService.class);
    enhancer.setCallback(new AutoMethodInterceptor());
    UserService userService = (UserService) enhancer.create();
    userService.saveUser();
}

CGLIB can proxy concrete classes, not just interfaces.

Javassist Proxy

Using Javassist, a class is generated at runtime. The article shows AssistByteCode that creates UserDaoImpl bytecode, adds the saveUser method, writes the class file, and loads it.

public class AssistByteCode {
    public static void createByteCode() throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass cc = classPool.makeClass("com.cxuan.proxypattern.UserDaoImpl");
        CtClass ctClass = classPool.get("com.cxuan.proxypattern.UserDao");
        cc.setInterfaces(new CtClass[]{ctClass});
        CtMethod saveUser = CtMethod.make("public void saveUser(){}", cc);
        saveUser.setBody("System.out.println(\"---- 插入用户 ----\");");
        cc.addMethod(saveUser);
        cc.toClass();
        cc.writeFile("/path/to/output");
    }
}

A proxy factory then creates a subclass with a method interceptor using Javassist.

public class JavaassistProxyFactory {
    public Object getProxy(Class clazz) throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(clazz);
        proxyFactory.setHandler((self, thisMethod, proceed, args) -> {
            System.out.println("---- 开始拦截 ----");
            Object result = proceed.invoke(self, args);
            System.out.println("---- 结束拦截 ----");
            return result;
        });
        return proxyFactory.createClass().newInstance();
    }
}

ASM Proxy

ASM generates raw bytecode. The article builds a TargetServiceProxy class that holds references to the target service and an aspect service, defines constructors, and a method demoQuest that invokes the aspect before delegating to the target.

public class AsmProxy extends ClassLoader implements Opcodes {
    public static void createAsmProxy() throws Exception {
        String targetServiceName = TargetService.class.getName().replace(".", "/");
        String aspectServiceName = AspectService.class.getName().replace(".", "/");
        String proxyServiceName = targetServiceName + "Proxy";
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_8, ACC_PUBLIC, proxyServiceName, null, targetServiceName, null);
        // fields, constructors, and method generation omitted for brevity
        byte[] code = cw.toByteArray();
        Class
clazz = new AsmProxy().defineClass(TargetService.class.getName() + "Proxy", code, 0, code.length);
        Constructor
ctor = clazz.getConstructor(TargetService.class, AspectService.class);
        Object obj = ctor.newInstance(new TargetService(), new AspectService());
        ((TargetService) obj).demoQuest();
    }
}

The article concludes that all four proxy techniques rely on Java reflection, each with different performance and maintenance trade‑offs. While JDK dynamic proxy may be slower in some scenarios, modern JDKs have optimized reflection, making the performance gap less significant. Selection should consider reliability, maintainability, and development effort.

Design PatternsJavaDynamic ProxyASMJavassistCGLIB
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.