﻿---
title: The Proxy Pattern in Java
date: 2024-10-22
tags:
  - Java
  - Reflection
  - Spring
cover: https://assets.vluv.space/cover/Dev/Java/代理模式.webp
excerpt: "The proxy pattern lets one object stand in for another, controlling access to it and adding features like logging, access control, lazy loading, or monitoring without touching the target's core logic."
updated: 2026-07-08 21:23:22
lang: en
i18n:
  cn: /proxy_pattern
  translation: 2
---

<script type="module" src="/js/components/tab.js"></script>

![alt text](https://assets.vluv.space/Java/代理模式/代理模式-1.webp)

**Where the proxy pattern is used**

- **AOP**: a proxy class intercepts calls to the target object's methods and enhances them.
- **Access control**: a proxy class restricts access to certain methods or resources.
- **Logging**: record log information when the target object's methods are called.
- **Performance monitoring**: measure method execution time in the proxy to find performance bottlenecks.
- **Lazy loading**: defer initialization of certain resources through a proxy object to save memory or improve performance.
- **Remote proxy**: provide a local proxy for remote calls, so calling a local method feels like calling a remote service.

> [!question]- Why introduce a proxy
>
> Implementing features like logging or performance monitoring in a proxy, rather than modifying the source code directly, effectively decouples those features from the business logic.
>
> **Single responsibility principle**:
> Put logging, performance monitoring, and the like in the proxy, and let the target class focus on business logic. This weakens the dependency between the business logic and [cross-cutting concerns](https://en.wikipedia.org/wiki/Cross-cutting_concern), in line with the single responsibility principle. If logging or monitoring requirements change later, say the log format, level, or storage, you only modify the proxy class instead of touching every target class.
> **Code reuse and consistency**:
> Reuse: one proxy class can serve multiple target classes, so you don't reimplement the same feature (like logging) in each one, reducing redundant code.
> Consistency: the same logging or monitoring logic applies across all target classes; every log follows the same format, which makes later analysis and monitoring easier.
> **Easy to extend and modify**
> Extension: to add new features (exception handling, input validation, and so on), implement them in the proxy without disturbing the target class's business logic. For example, you can add performance monitoring on top of logging just by extending the invoke method.
> Flexibility: you can decide at runtime which proxy to use. Different proxy implementations can add different features without modifying the target class, which also reduces conflicts when working in a team.

In Java, the proxy pattern comes in two main flavors: **static proxies** and **dynamic proxies**. Both control access to the target object, but they are implemented differently.

## Static Proxy

Suppose you have an interface `BankAccount` that defines 3 methods, one of which is `withdraw()`.

Suppose a class `BankAccountImpl` implements this interface and handles the actual withdrawal. You can then create a proxy class `BankAccountProxy` that also implements the interface, but adds extra operations before and after calling `BankAccountImpl`'s `withdraw` method, such as logging or permission checks.

**Steps**

- Define the interface, `BankAccount Interface`
- Define a class implementing it, `BankAccountImpl implements BankAccount`
- Write the proxy class, `BankAccountProxy`
- Call `BankAccountProxy`'s constructor to create a static proxy instance

<x-tabs>

<x-tab title="AccoutService" active>

```java
package proxydemo;
// Account service interface defining financial operations
public interface BankAccount {
    void viewBalance(); // view balance
    void withdraw(); // withdraw
    void deposit(); // deposit
}
```

</x-tab>

<x-tab title="BankAccountImpl">

```java
package proxydemo;
// Account service implementation
public class BankAccountImpl implements BankAccount {

    @Override
    public void viewBalance() {
        System.out.println("Displaying account balance.");
    }

    @Override
    public void withdraw() {
        System.out.println("Withdrawing funds from account.");
    }

    @Override
    public void deposit() {
        System.out.println("Depositing funds into account.");
    }
}
```

</x-tab>

<x-tab title="BankAccountProxy">

```java
package proxydemo;

// Account service proxy class
// The proxy controls access to the real account service operations.
// Only the ADMIN role may perform withdraw(), deposit() and transfer(),
// while regular users can only view the balance and exit the service.

public class BankAccountProxy implements BankAccount {
    private BankAccountImpl BankAccount;
    private String userRole;

    // Constructor taking the real account service object and the user role
    public BankAccountProxy(BankAccountImpl BankAccount, String userRole) {
        this.BankAccount = BankAccount;
        this.userRole = userRole;
        System.out.println("BankAccountProxy initialized with user role: " + userRole);
    }

    @Override
    public void viewBalance() {
        System.out.println("User with role " + userRole + " is viewing balance.");
        BankAccount.viewBalance(); // every user may view the balance
    }

    @Override
    public void withdraw() {
        if ("ADMIN".equals(userRole)) {
            BankAccount.withdraw(); // only admins may withdraw
        } else {
            System.err.println("Access denied for user with role " + userRole + ": Only ADMIN can withdraw funds.");
        }
    }

    @Override
    public void deposit() {
        if ("ADMIN".equals(userRole)) {
            BankAccount.deposit(); // only admins may deposit
        } else {
            System.err.println("Access denied for user with role " + userRole + ": Only ADMIN can deposit funds.");
        }
    }
}
```

</x-tab>

<x-tab title="Main">

```java
package proxydemo;

public class Main {
    public static void main(String[] args) {
        BankAccountImpl BankAccount = new BankAccountImpl();

        // Create a proxy for an admin user
        System.out.println("Admin user:");
        BankAccount adminProxy = new BankAccountProxy(BankAccount, "ADMIN");
        adminProxy.viewBalance(); // admin can view balance
        adminProxy.withdraw(); // admin can withdraw
        adminProxy.deposit(); // admin can deposit

        // Create a proxy for a regular user
        System.out.println("\nRegular user:");
        BankAccount userProxy = new BankAccountProxy(BankAccount, "USER");
        userProxy.viewBalance(); // regular user can view balance
        userProxy.withdraw(); // regular user cannot withdraw
        userProxy.deposit(); // regular user cannot deposit
    }
}
```

</x-tab>

</x-tabs>

---

```shell
Admin user:
BankAccountProxy initialized with user role: ADMIN
User with role ADMIN is viewing balance.
Displaying account balance.
Withdrawing funds from account.
Depositing funds into account.

Regular user:
BankAccountProxy initialized with user role: USER
User with role USER is viewing balance.
Displaying account balance.
Access denied for user with role USER: Only ADMIN can withdraw funds.
Access denied for user with role USER: Only ADMIN can deposit funds.
```

**Pros**:

- You can add new features without modifying the `BankAccountImpl` class; coupling between the proxy class and the target class stays low.

**Cons**:

- If the target class has many methods, writing the proxy class by hand is tedious.
- The proxy class carries a lot of code, and every change has to be maintained by hand.

## Dynamic Proxy

A static proxy is fixed at compile time: the proxy class's bytecode is already determined, and every class needs its own proxy class, which leads to redundant code. A dynamic proxy skips writing each proxy class by hand; the proxy class is generated at runtime and written to a class file, so there is less redundancy than with a static proxy.

Java offers two main kinds of dynamic proxy:

- **JDK dynamic proxy**: interface-based; it can only proxy classes that implement an interface.
- **CGLIB dynamic proxy**: inheritance-based; it can proxy classes that implement no interface.

### JDK Dynamic Proxy

JDK dynamic proxies are built on `java.lang.reflect.Proxy` and `InvocationHandler`.

- When a method on the proxy class is called, the call is delegated to the `invoke` method of the `InvocationHandler` instance.
- The `Proxy` class creates the proxy object via reflection, based on the interfaces the target class implements and the `InvocationHandler` interface.

I won't go into how JDK dynamic proxies work under the hood here; if you're curious, see [彻底明白 JDK 动态代理的底层原理](https://zhuanlan.zhihu.com/p/616987288).

**Steps**

- Define the interface, `BankAccount Interface`
- Define a class implementing it, `BankAccountImpl implements BankAccount`
- Create an implementation of the `InvocationHandler` interface
- Use `java.lang.reflect.Proxy`'s `newProxyInstance` method to create a dynamic proxy instance

<x-tabs>

<x-tab title="BankAccount Interface" active>

```java
package proxydemo;
// Account service interface defining financial operations
public interface BankAccount {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}
```

</x-tab>

<x-tab title="BankAccountImpl">

```java
package proxydemo;

// Account service implementation
public class BankAccountImpl implements BankAccount {
    private double balance;

    @Override
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited: " + amount);
    }

    @Override
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrew: " + amount);
        } else {
            System.out.println("Insufficient balance");
        }
    }

    @Override
    public double getBalance() {
        return balance;
    }
}
```

</x-tab>

<x-tab title="InvocationHandler">

**Defining the JDK dynamic proxy class**

`InvocationHandler` is an interface in Java's reflection package java.lang.reflect that handles method calls on proxy instances.

```java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class BankAccountProxyHandler implements InvocationHandler {
    private final BankAccount target;

    public BankAccountProxyHandler(BankAccount target) {
        this.target = target;
    }

    /**
     * Triggered when a method is invoked on the proxy instance. It receives three arguments: the proxy instance, the invoked Method object, and the argument array. This method handles the call and returns the result.
     *
     * @param proxy  the proxy object itself; usually not used directly inside the method.
     * @param method the target method being invoked; a Method object wrapping its metadata (name, parameter types, etc.).
     * @param args   the actual arguments passed to the target method, as an Object[] array; null if there are none.
     * @return the method's return value (if it has one).
     * @throws Throwable rethrown if invoking the target object's method throws.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // You can do work before invoking the method;
        // for example, add log output or record the start time before the target method runs
        long startTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " is called with arguments: " + (args != null ? java.util.Arrays.toString(args) : "[]"));
        // Use the Method object to invoke the target (real) object's method, passing args
        Object result = method.invoke(target, args);
        // Likewise, you can do work after the method call;
        // for example, add a log entry afterwards
        long endTime = System.currentTimeMillis();
        System.out.println("Method " + method.getName() + " executed in " + (endTime - startTime) + " ms");

        // Return the result (the method's return value if any, otherwise null)
        return result;
    }
}
```

</x-tab>

<x-tab title="Main">

The Proxy class is a key class in the Java reflection API, in the `java.lang.reflect` package. Its main job is to create proxy objects dynamically, that is, to generate at runtime a proxy class implementing given interfaces.

`{java} Proxy.newProxyInstance` is the core method for creating dynamic proxy objects. It takes three arguments:

- A class loader `ClassLoader`: the class loader used to load the proxy class, usually the target class's class loader.
- An interface array `{java} Class<?>[]`: the interfaces the proxy class should implement. The proxy object will implement all methods in these interfaces.
- An `InvocationHandler` instance: an InvocationHandler responsible for handling method calls on the proxy object.

```java
package proxydemo;

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // Create the real bank account object
        BankAccount realAccount = new BankAccountImpl();
        // Create the proxy object
        BankAccount proxyAccount = (BankAccount) JdkProxyFactory.getProxy(realAccount);
        // Call methods through the proxy
        proxyAccount.deposit(100);
        proxyAccount.withdraw(50);
        System.out.println("Balance: " + proxyAccount.getBalance());
    }
}

class JdkProxyFactory {
    public static Object getProxy(Object target) { // target: real object which implements BankAccount Interface
        // Create the dynamic proxy object via the Proxy class
        // arg 1: class loader, usually the target object's class loader
        // arg 2: interfaces implemented by the target object; multiple allowed
        // arg 3: the InvocationHandler implementation
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new BankAccountProxyHandler((BankAccount) target));
    }
}
```

</x-tab>

</x-tabs>

---

```shell
Method deposit is called with arguments: [100.0]
Deposited: 100.0
Method deposit executed in 20 ms
Method withdraw is called with arguments: [50.0]
Withdrew: 50.0
Method withdraw executed in 1 ms
Method getBalance is called with arguments: []
Method getBalance executed in 3 ms
Balance: 50.0
```

### CGLIB Dynamic Proxy

[CGLIB (Code Generation Library)](https://mvnrepository.com/artifact/cglib/cglib) relies on the ASM library, letting us modify and generate bytecode at runtime. CGLIB implements proxying through inheritance.

![](https://assets.vluv.space/Java/代理模式/代理模式.webp)

> CGLIB is used within the Spring framework.
> Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into spring-core).
> If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.

**CGLIB vs. JDK dynamic proxy**

- What can be proxied:
  - JDK dynamic proxy: only classes that implement an interface.
  - CGLIB dynamic proxy: plain classes (with no interface).
- How it works:
  - JDK dynamic proxy: via reflection; the proxy class implements the target object's interfaces.
  - CGLIB dynamic proxy: by generating a subclass of the target class.
- Limitations:
  - CGLIB cannot proxy final classes or final methods, because it works by generating subclasses and final methods cannot be overridden.

**Steps**

- Add the CGLIB dependency
- Create the target class, `BankAccount`
- Create a MethodInterceptor implementation to handle the interception logic
- Generate the proxy object: create the proxy instance via CGLIB

<x-tabs>

<x-tab title="BankAccount" active>

```java
public class BankAccount {
    public void deposit(double amount) {
        System.out.printf("Depositing: " + amount);
    }

    public void withdraw(double amount) {
        System.out.printf("Withdrawing: " + amount);
    }
}
```

</x-tab>

<x-tab title="MethodInterceptor">

```java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class BankAccountInterceptor implements MethodInterceptor {
    /**
     * Interceptor method
     *
     * @param proxyObj the proxy object
     * @param method the proxied method
     * @param args the method arguments
     * @param proxy the method's proxy, used to invoke the superclass method
     * @return the method's return value
     * @throws Throwable any exception thrown
     */
    @Override
    public Object intercept(Object proxyObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Method" + method.getName() + "is being called with arguments: " + java.util.Arrays.toString(args));
        Object res = proxy.invoke(proxyObj, args);
        System.out.println("Method" + method.getName() + "execution completed");
        return res;
    }
}
```

</x-tab>

<x-tab title="Main">

```java
import net.sf.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        // Create the dynamic proxy class
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(BankAccount.class);
        enhancer.setCallback(new BankAccountInterceptor());

        // Create the proxy object
        BankAccount proxyAccount = (BankAccount)enhancer.create();
        // Call methods through the proxy
        proxyAccount.deposit(100);
        proxyAccount.withdraw(50);
    }
}
```

</x-tab>

</x-tabs>

---

```shell
Method deposit is being called with arguments: [100.0]
Depositing: 100.0
Method deposit execution completed.
Method withdraw is being called with arguments: [50.0]
Withdrawing: 50.0
Method withdraw execution completed.
```

## Ref

[Proxy Java SE 21 & JDK 21](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Proxy.html)
