Java 代理模式
Intro
代理模式是一种设计模式,允许一个对象充当另一个对象的替代或中介。代理对象可以控制对目标对象的访问,在不修改原目标对象核心逻辑的情况下,实现额外的功能,如日志记录、权限控制或延迟加载,甚至监控等;
代理模式的应用场景
- AOP: 通过代理类来拦截目标对象方法的调用,实现对目标对象方法的增强。
- 权限校验:通过代理类来控制对某些方法或资源的访问。
- 日志记录:在调用目标对象的方法时,记录相关日志信息;
- 性能监控:在代理类中记录方法的执行时间,便于分析性能瓶颈。
- 懒加载:通过代理对象延迟初始化某些资源,以节省内存或提高性能。
- 远程代理:为远程调用提供一个本地代理,使得本地方法调用就像调用远程服务一样。
在 Java 中,代理模式主要分为两种:静态代理和动态代理。这两种类型的代理虽然都能够控制对目标对象的访问,但它们的实现方式有所不同。
Static Proxy
假设你有一个接口 BankAccount
,它定义了 3 个方法。其中一个方法为withdraw()
假设有一个实现了这个接口的类 BankAccountImpl
,它负责具体的取钱操作。现在,你可以创建一个代理类BankAccountProxy
,它也实现了该接口,但在调用 BankAccountImpl
的withdraw
方法前后,增加一些额外的操作,比如记录日志或进行权限检查。
Steps
- 定义接口,
BankAccount Interface
- 定义 class 实现接口,
BankAccountImpl implements BankAccount
- 编写代理类,
BankAccountProxy
- 调用
BankAccountProxy
的构造器,创建静态代理对象实例
package proxydemo;// 账户服务接口,定义金融操作public interface BankAccount { void viewBalance(); // 查看余额 void withdraw(); // 取款 void deposit(); // 存款}
package proxydemo;// 账户服务实现类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."); }}
package proxydemo;// 账户服务代理类// 代理类负责控制对实际账户服务操作的访问。只有管理员角色才能执行// withdraw()、deposit()和 transfer()操作,而普通用户只能查看余额// 和退出服务。public class BankAccountProxy implements BankAccount { private BankAccountImpl BankAccount; private String userRole; // 构造方法,传入实际的账户服务对象和用户角色 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(); // 所有用户都可以查看余额 } @Override public void withdraw() { if ("ADMIN".equals(userRole)) { BankAccount.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(); // 只有管理员可以存款 } else { System.err.println("Access denied for user with role " + userRole + ": Only ADMIN can deposit funds."); } }}
package proxydemo;public class Main { public static void main(String[] args) { BankAccountImpl BankAccount = new BankAccountImpl(); // 创建管理员用户的代理 System.out.println("Admin user:"); BankAccount adminProxy = new BankAccountProxy(BankAccount, "ADMIN"); adminProxy.viewBalance(); // 管理员可以查看余额 adminProxy.withdraw(); // 管理员可以取款 adminProxy.deposit(); // 管理员可以存款 // 创建普通用户的代理 System.out.println("\nRegular user:"); BankAccount userProxy = new BankAccountProxy(BankAccount, "USER"); userProxy.viewBalance(); // 普通用户可以查看余额 userProxy.withdraw(); // 普通用户无法取款 userProxy.deposit(); // 普通用户无法存款 }}
Admin user:BankAccountProxy initialized with user role: ADMINUser with role ADMIN is viewing balance.Displaying account balance.Withdrawing funds from account.Depositing funds into account.Regular user:BankAccountProxy initialized with user role: USERUser 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.
优点:
- 可以在不修改
BankAccountImpl
类的情况下,添加新的功能;代理类与目标类之间的耦合度低。
缺点:
- 如果目标类有很多方法,手动编写代理类会非常繁琐。
- 代理类的代码量较多,且每次更改都需要手动维护。
Dynamic Proxy
静态代理是在编译期间就需要完成的,即代理类的字节码都已经确定,每个类都要创建一个代理类,导致代码冗余。动态代理不需要手动编写每个代理类,而是在运行时才会生成代理类,并写入 class 文件,相较于静态代理代码冗余更少。
Java 中的动态代理主要有两种方式:
- JDK 动态代理:基于接口的代理,它只能代理实现了接口的类。
- CGLIB 动态代理:基于继承的代理,它可以代理没有实现接口的类。
JDK 动态代理
Java JDK 动态代理是基于 java.lang.reflect.Proxy
和 InvocationHandler
实现的。
- 当调用代理类的方法时,会将其委托给
InvocationHandler
实例的invoke
方法来运行。 Proxy
类基于目标类实现的接口和InvocationHandler
接口,通过反射机制来创建代理类对象。
JDK 动态代理的原理在这里不赘述,感兴趣可参考彻底明白 JDK 动态代理的底层原理
Steps
- 定义接口,
BankAccount Interface
- 定义 class 实现接口,
BankAccountImpl implements BankAccount
- 创建
InvocationHandler
接口的实现类 - 使用
java.lang.reflect.Proxy
的newProxyInstance
方法创建动态代理对象的实例
package proxydemo;// 账户服务接口,定义金融操作public interface BankAccount { void deposit(double amount); void withdraw(double amount); double getBalance();}
package proxydemo;// 账户服务实现类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; }}
定义 JDK 动态代理类
InvocationHandler
是 Java 反射包 java.lang.reflect 中的一个接口,用于处理代理实例的方法调用。
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; } /** * 当代理实例上的方法被调用时,这个方法会被触发。它接收三个参数:代理实例、被调用的方法对象和方法参数数组。该方法负责处理方法调用并返回结果 * * @param proxy 代理对象本身。通常在方法中不会直接使用它。 * @param method 表示正在调用的目标方法。它是一个 Method 对象,封装了该方法的元数据信息(如方法名、参数类型等)。 * @param args 目标方法调用时传入的实际参数,是一个 Object[] 数组。如果没有参数,则为 null。 * @return method的返回值(如果有返回值)。 * @throws Throwable 如果调用目标对象的方法抛出异常,则会向上抛出异常。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 可以在调用方法之前执行部分操作; // 例如,在目标方法调用前添加日志信息,记录执行时间等等 long startTime = System.currentTimeMillis(); System.out.println("Method " + method.getName() + " is called with arguments: " + (args != null ? java.util.Arrays.toString(args) : "[]")); // 使用 Method 对象调用目标对象(真实对象)的方法,并传递参数 args Object result = method.invoke(target, args); // 同样地,可以在调用方法之后执行部分操作; // 例如,在方法调用后添加日志 long endTime = System.currentTimeMillis(); System.out.println("Method " + method.getName() + " executed in " + (endTime - startTime) + " ms"); // 返回调用结果(如果方法有返回值,则返回该值;如果没有返回值,返回 null) return result; }}
Proxy 类是 Java 反射 API 中的一个重要类,位于 java.lang.reflect
包内。它的主要作用是用来动态创建代理对象,即在运行时生成一个实现特定接口的代理类
Proxy.newProxyInstance
是创建动态代理对象的核心方法。它接受三个参数,分别是:
- 类加载器
ClassLoader
:用于加载代理类的类加载器。通常是目标类的类加载器。 - 接口数组
Class<?>[]
:指定代理类要实现的接口。代理对象将实现这些接口中的所有方法。 InvocationHandler
实例:一个 InvocationHandler 的实例,它负责处理代理对象的方法调用。
package proxydemo;import java.lang.reflect.Proxy;public class Main { public static void main(String[] args) { // 创建银行账户的真实对象 BankAccount realAccount = new BankAccountImpl(); // 创建代理对象 BankAccount proxyAccount = (BankAccount) JdkProxyFactory.getProxy(realAccount); // 使用代理对象执行方法 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 // 通过 Proxy 类创建动态代理对象 // 参数1:类加载器,通常使用目标对象的类加载器 // 参数2:目标对象实现的接口,可指定多个 // 参数3:InvocationHandler 实现类 return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new BankAccountProxyHandler((BankAccount) target)); }}
Method deposit is called with arguments: [100.0]Deposited: 100.0Method deposit executed in 20 msMethod withdraw is called with arguments: [50.0]Withdrew: 50.0Method withdraw executed in 1 msMethod getBalance is called with arguments: []Method getBalance executed in 3 msBalance: 50.0
CGLIB 动态代理
CGLIB(Code Generation Library)依赖于 ASM 库,允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。

CGLIB 在 Spring 框架中有所应用
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 与 JDK 动态代理的区别
- 代理对象类型:
- JDK 动态代理:只能代理实现了接口的类。
- CGLIB 动态代理:可以代理普通的类(没有实现接口的类)。
- 实现方式:
- JDK 动态代理:通过反射实现,代理类实现目标对象的接口。
- CGLIB 动态代理:通过生成目标类的子类来实现。
- 限制:
- CGLIB 不能代理 final 类和 final 方法,因为它是通过生成子类来实现的,而 final 方法不能被重写。
Steps
- 引入 CGLIB 依赖
- 创建目标类,
BankAccount
- 创建 MethodInterceptor 实现类:处理方法拦截逻辑。
- 生成代理对象:通过 CGLIB 创建代理对象的实例
public class BankAccount { public void deposit(double amount) { System.out.printf("Depositing: " + amount); } public void withdraw(double amount) { System.out.printf("Withdrawing: " + amount); }}
import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class BankAccountInterceptor implements MethodInterceptor { /** * 拦截器方法 * * @param proxyObj 代理对象 * @param method 被代理的方法 * @param args 方法的参数 * @param proxy 方法的代理对象,用于调用父类的方法 * @return 方法的返回值 * @throws Throwable 抛出的异常 */ @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; }}
import net.sf.cglib.proxy.Enhancer;public class Main { public static void main(String[] args) { // 创建动态代理类 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(BankAccount.class); enhancer.setCallback(new BankAccountInterceptor()); // 创建代理对象 BankAccount proxyAccount = (BankAccount)enhancer.create(); // 通过代理对象调用方法 proxyAccount.deposit(100); proxyAccount.withdraw(50); }}
Method deposit is being called with arguments: [100.0]Depositing: 100.0Method deposit execution completed.Method withdraw is being called with arguments: [50.0]Withdrawing: 50.0Method withdraw execution completed.
Ref
Java 代理模式