Fundamentals 21 min read

Understanding Java Dynamic Proxy: Static vs Dynamic Implementations and Tools

This article explains the proxy pattern in Java, compares static and dynamic proxy implementations, provides complete code examples for JDK dynamic proxies, CGLIB, Javassist, and ASM proxies, and discusses their characteristics, performance considerations, and practical usage scenarios.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding Java Dynamic Proxy: Static vs Dynamic Implementations and Tools

This article introduces the proxy pattern in Java, a structural design pattern that allows an object to delegate operations to another object, enabling additional behavior such as AOP, RPC, logging, transaction handling, and global exception processing without modifying the original source code.

Static Proxy – The article first presents a static proxy example where a UserDao interface, its implementation UserDaoImpl , and a proxy class UserProxy are defined. The proxy manually holds a reference to the real object and adds pre‑ and post‑processing around the saveUser() method. The complete source code is shown:

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();
    }
}

The static proxy is compiled at build time, knows the target class in advance, and typically proxies a single class.

JDK Dynamic Proxy – The article then modifies the static example to use a dynamic proxy based on java.lang.reflect.Proxy . An UserHandler class implements InvocationHandler and intercepts method calls to add custom logic before and after invoking the real method:

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();
}

This JDK dynamic proxy works for any interface, determines the target at runtime, and avoids the need to write separate proxy classes for each target.

Comparison – The article summarizes the main differences: static proxies are generated at compile time, know the target class, and usually proxy a single class; dynamic proxies are created at runtime via reflection, can proxy all implementations of an interface, and require an InvocationHandler implementation.

Other Proxy Technologies – The article briefly surveys three additional bytecode‑generation libraries:

CGLIB – Generates a subclass of the target class at runtime, suitable for classes without interfaces. Example Maven dependency and a simple UserService class are shown, followed by an AutoMethodInterceptor that overrides methods via MethodInterceptor and a test using Enhancer :

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

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();
}

Javassist – Uses a high‑level API to create or modify classes at runtime. The article shows a AssistByteCode class that builds a UserDaoImpl class programmatically, and a JavaassistProxyFactory that creates a proxy by defining a handler with a lambda expression.

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();
    }
}

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 – A low‑level bytecode manipulation framework. The article provides a lengthy example that builds a TargetServiceProxy class byte by byte, injects fields for the target and aspect services, creates constructors, and defines a demoQuest method that invokes the aspect before delegating to the target.

public class AsmProxy extends ClassLoader implements Opcodes {
    public static void createAsmProxy() throws Exception {
        // ... bytecode generation using ClassWriter, MethodVisitor, etc.
        byte[] code = classWriter.toByteArray();
        Class
clazz = (new AsmProxy()).defineClass(TargetService.class.getName() + "Proxy", code, 0, code.length);
        Constructor
constructor = clazz.getConstructor(TargetService.class, AspectService.class);
        Object object = constructor.newInstance(new TargetService(), new AspectService());
        // ... invoke demoQuest and write .class file
    }
}

All four approaches ultimately achieve the same goal—adding behavior around method execution—but differ in how the proxy class is generated, what kinds of targets they can handle, and their runtime performance characteristics.

The article concludes that while JDK dynamic proxies may be slightly slower than CGLIB or Javassist in some benchmarks, modern JDKs have optimized reflection, and the choice should be based on reliability, maintainability, and development effort rather than raw speed.

Design PatternsJavaJDKDynamic 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.