在 Java 开发中,代理模式是一种重要的结构型设计模式,其核心思想是为目标对象提供一个代理对象 ,由代理对象控制对目标对象的访问。代理模式可以在不修改目标对象源码 的前提下,对目标对象的方法进行增强(如添加日志、权限校验、事务管理等)。
本文将详细讲解 Java 中三种常见的代理方式:静态代理、JDK 动态代理和 CGLib 动态代理,并对比它们的特性与适用场景。
在正式讲解代码前,先明确代理模式的三个核心角色,后续所有实现都会围绕这三个角色展开:
-
目标接口:定义业务方法的规范,是代理类和目标类的"约定",明确了代理能代理哪些方法;
-
目标类(被代理类):实现目标接口,是真正执行业务逻辑的类,比如下文代码示例中用户服务中"添加用户"的核心逻辑就封装在这里;
-
代理类:持有目标类的引用,实现目标接口(或继承目标类),在目标方法的前后添加增强逻辑,最终对外提供服务。
代理模式的核心优势是"解耦"------将通用增强逻辑(如日志、权限)与核心业务逻辑分离,核心业务类只关注自身功能,通用逻辑统一由代理类管理,便于维护和扩展。
静态代理
静态代理是最基础的实现方式,特点是在编译时手动编写代理类,代理类与目标类一一对应,对同名业务方法进行包装。
核心原理
静态代理要求代理类和目标类实现同一个目标接口,代理类内部持有目标类的引用,通过构造器接收目标对象;当调用代理类的方法时,会先执行增强逻辑,再调用目标对象的核心业务方法,最后执行后置增强逻辑。
代码示例
定义目标接口,声明业务方法:添加用户的方法
java
public interface UserService {
//添加用户
void addUser(String username);
}
实现目标类(业务实现类),实现目标接口,执行业务逻辑:模拟的添加用户操作
java
public class UserServiceImpl implements UserService{
@Override
public void addUser(String username) {
System.out.println("添加用户:" + username);
}
}
定义静态代理类,实现目标接口,持有目标对象,包装业务方法
java
public class UserServiceStaticProxy implements UserService{
// 持有目标对象,任何实现了这个接口的类,都能被这个代理类代理
private UserService target;
// 通过构造器注入目标对象,强制传入目标对象,避免空指针,让代理知道要代理谁
public UserServiceStaticProxy(UserService target) {
this.target = target;
}
// 重写addUser方法:这是代理的核心,包装目标方法
@Override
public void addUser(String username) {
// 前置增强:调用目标方法前的逻辑
System.out.println("静态代理 - 前置:校验添加用户权限");
// 调用目标对象的业务方法,这是核心,代理类不做实际业务,只是转发
target.addUser(username);
// 后置增强:调用目标方法后的逻辑
System.out.println("静态代理 - 后置:记录添加用户日志");
}
}
测试,使用代理对象访问目标对象
java
public class StaticProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理对象并传入目标对象
UserService proxy = new UserServiceStaticProxy(target);
// 通过代理对象调用方法,用户只和代理交互,不直接接触目标对象
proxy.addUser("张三");
}
}
运行结果

代码细节解释
(1)目标接口UserService
定义了addUser方法,是代理类和目标类的"约定"。为什么要定义接口?因为静态代理的核心是"面向接口编程",后续代理类和目标类都实现该接口,保证方法名、参数一致,同时提升灵活性(后续可替换目标类实现)。
(2)目标类UserServiceImpl
实现UserService接口,重写addUser方法,这里封装的是"添加用户"的核心业务逻辑------这是整个程序的核心功能,不包含任何通用增强逻辑,符合"单一职责原则"。
(3)代理类UserServiceStaticProxy
-
private UserService target;:声明目标对象引用,类型是接口UserService而非实现类UserServiceImpl。原因是"面向接口编程",如果声明为实现类,后续新增其他实现类(如带缓存的UserServiceCacheImpl)时,代理类无法复用,只能重新编写,耦合度极高;而声明为接口,任何实现该接口的类都能被代理,灵活性更强。 -
构造器
public UserServiceStaticProxy(UserService target):通过构造器注入目标对象,这是"依赖注入"的基础。作用是强制创建代理对象时必须传入目标对象,避免target为空导致空指针异常,同时让代理类与目标类建立关联。 -
重写
addUser方法:这是代理的核心逻辑。先执行前置增强(权限校验),再通过target.addUser(username)调用目标对象的核心业务方法,最后执行后置增强(日志记录)------用户调用的是代理类的方法,全程不直接接触目标对象,实现了对目标对象的控制。
(4)测试类StaticProxyTest
-
UserService target = new UserServiceImpl();:创建目标对象,这是真正执行业务的对象,相当于"干活的人"。 -
UserService proxy = new UserServiceStaticProxy(target);:创建代理对象,将目标对象传入,相当于"找个中介,告诉中介要帮谁干活"。 -
proxy.addUser("张三");:调用代理对象的方法,用户只与中介(代理)交互,中介负责完成增强逻辑和核心业务逻辑的调用,实现了对目标对象的"隔离"。
静态代理的特点
- 优点:编译时生成 class 字节码文件,直接使用,执行效率高;逻辑简单,易于理解和调试。
- 缺点:代理类与目标类一一对应,当业务接口有多个方法或多个目标类时,需要编写大量代理类,代码冗余度高;一旦接口发生变更,目标类和代理类都需要修改,维护成本高;灵活性差,无法动态适配不同的目标类。
动态代理
JDK 动态代理
为解决静态代理的代码冗余问题,Java提供了动态代理机制------JDK动态代理。其核心特点是运行时动态生成代理类字节码,无需手动编写代理类,一个代理处理器可适配多个目标类(只要实现接口)。
核心原理
JDK动态代理依赖Java反射机制 ,核心是**InvocationHandler** 接口(代理处理器)和**Proxy** 类。目标类必须实现至少一个接口,Proxy.newProxyInstance()方法会在运行时生成一个实现目标接口的代理类字节码;当调用代理对象的方法时,会触发InvocationHandler的invoke方法,在该方法中统一处理增强逻辑和目标方法调用。
代码示例
首先复用之前的UserService接口和UserServiceImpl实现类
定义JDK动态代理处理器,实现InvocationHandler,统一处理增强逻辑
java
package com.qcby.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 定义JDK动态代理处理器
*/
public class JdkDynamicProxyHandler implements InvocationHandler {
// 持有目标对象(通用类型,适配不同接口)
private Object target;
public JdkDynamicProxyHandler(Object target) {
this.target = target;
}
/**
* 代理逻辑核心方法
*
* @param proxy 动态生成的代理对象(一般不用)
* @param method 被代理的方法
* @param args 目标方法入参
* @return 方法执行结果
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("JDK动态代理 - 前置:校验" + method.getName() + "方法权限");
// 通过反射调用目标对象的方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("JDK动态代理 - 后置:记录" + method.getName() + "方法日志");
return result;
}
/**
* 生成动态代理对象的入口方法
* @return 动态生成的代理对象(实现目标接口)
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器,用于加载动态生成的代理类字节码
target.getClass().getInterfaces(), // 目标类实现的接口,JDK代理的核心,代理类会实现这些接口
this // 当前InvocationHandler实例:告诉代理类增强逻辑在哪里
);
}
}
测试
java
public class JdkDynamicProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService target = new UserServiceImpl();
// 创建代理处理器,传入目标对象
JdkDynamicProxyHandler handler = new JdkDynamicProxyHandler(target);
// 生成动态代理对象:JDK在运行时自动创建代理类字节码并实例化
UserService proxy = (UserService) handler.getProxyInstance();
// 调用代理方法,触发invoke方法
proxy.addUser("李四");
}
}
运行结果

代码细节解释
(1)核心接口InvocationHandler
这是JDK动态代理的"增强逻辑处理器",所有的增强逻辑都写在其invoke方法中。任何JDK动态代理都必须实现这个接口,它是代理类和增强逻辑的桥梁。
(2)代理处理器JdkDynamicProxyHandler
-
private Object target;:用Object类型持有目标对象,原因是该处理器要适配所有实现接口的目标类(如UserService、OrderService等),若写死为UserService,就只能代理这一个接口,失去了动态代理的灵活性。 -
构造器
public JdkDynamicProxyHandler(Object target):和静态代理一样,通过构造器注入目标对象,建立处理器与目标对象的关联,避免空指针。 -
核心方法
invoke(Object proxy, Method method, Object[] args):这是JDK动态代理的核心,所有代理对象的方法调用都会触发此方法:-
proxy:动态生成的代理对象(如$Proxy0),一般不用,因为我们关注的是目标方法的调用,而非代理对象本身; -
method:被代理的目标方法对象,通过它可以获取方法名(method.getName())、参数类型等信息,实现通用增强逻辑(如对所有方法统一加日志); -
args:目标方法的入参,需要传递给目标方法; -
method.invoke(target, args):通过反射调用目标对象的方法,这是JDK动态代理的核心机制。反射的作用是"动态找到并调用目标方法",无需提前知道具体方法名,因此能适配多个目标类; -
前置/后置增强:和静态代理逻辑一致,只是这里的增强逻辑是通用的,可复用在所有代理的目标方法上。
-
-
getProxyInstance():生成代理对象的入口,核心是Proxy.newProxyInstance()方法,该方法接收三个参数:-
参数1(类加载器):目标类的类加载器,用于加载运行时动态生成的代理类字节码(代理类是临时的,只存在于内存中);
-
参数2(接口数组):目标类实现的所有接口,JDK动态代理的核心限制------代理类会实现这些接口,因此代理对象才能强转为目标接口类型(如UserService);
-
参数3(InvocationHandler):当前处理器实例,告诉代理类"增强逻辑在哪里",代理对象调用方法时会触发该处理器的invoke方法。
-
(3)测试类JdkDynamicProxyTest
-
UserService target = new UserServiceImpl();:创建目标对象,和静态代理一致; -
JdkDynamicProxyHandler handler = new JdkDynamicProxyHandler(target);:创建处理器,传入目标对象,相当于"找个万能中介,告诉中介要帮谁干活"; -
UserService proxy = (UserService) handler.getProxyInstance();:生成代理对象,这里的proxy是JDK在运行时动态生成的代理类实例(类名类似$Proxy0),因为代理类实现了UserService接口,所以能强转为UserService类型; -
proxy.addUser("李四");:调用代理对象方法,触发handler的invoke方法,执行增强逻辑和目标方法调用。
JDK 动态代理的特点
- 优点:无需手动编写代理类,一个处理器可代理多个接口的类,减少代码冗余;运行时动态生成代理,灵活性极高,符合"开闭原则"(新增目标类无需修改处理器);
- 缺点:依赖反射机制调用方法,反射调用方法的性能略低于静态代理和 CGLib;只能代理实现了接口的类,无法代理普通类(无接口)。
CGLib 动态代理
JDK动态代理无法代理无接口的普通类,因此第三方库CGLib(Code Generation Library)弥补了这一不足。CGLib的核心特点是通过继承目标类生成子类作为代理类,无需目标类实现接口,运行时动态生成子类字节码,通过重写目标方法实现代理。
核心原理
CGLib依赖ASM字节码框架,直接操作字节码生成目标类的子类(代理类);代理类重写目标类的所有非final方法,当调用代理对象的方法时,会触发**MethodInterceptor** (方法拦截器)的**intercept**方法,在该方法中处理增强逻辑和目标方法调用。由于是继承实现,目标类和目标方法不能是final(final类无法继承,final方法无法重写)。
代码示例
CGLib是第三方库,需手动引入依赖(Spring等框架已内置CGLib,可直接使用)。Maven依赖如下:
XML
<!-- Maven依赖(如需手动引入) -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
定义普通业务类(无需实现接口)
java
public class OrderService {
// 非final方法:CGLib需要重写该方法
public void createOrder(String orderNo) {
System.out.println("创建订单:" + orderNo);
}
}
定义CGLib方法拦截器:实现MethodInterceptor,处理增强逻辑
java
package com.qcby.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 定义CGLib代理拦截器
public class CglibDynamicProxyInterceptor implements MethodInterceptor {
// 持有目标对象
private Object target;
public CglibDynamicProxyInterceptor(Object target) {
this.target = target;
}
/**
* 创建CGLib代理对象(目标类的子类)
* @return 代理对象(子类实例)
*/
public Object getProxyInstance() {
// Enhancer:CGLib的核心类,用于生成目标类的子类(代理类)
Enhancer enhancer = new Enhancer();
// 设置父类(目标类),代理类是该类的子类
enhancer.setSuperclass(target.getClass());
// 设置回调方法(拦截器),代理类的方法调用都会触发当前拦截器的intercept方法
enhancer.setCallback(this);
// 生成并返回代理对象(子类实例)
return enhancer.create();
}
/**
* 拦截方法(代理逻辑核心),所有代理对象的方法调用都会触发此方法
* @param proxy 代理对象(目标类的子类实例)
* @param method 被拦截的目标方法(父类方法)
* @param args 方法入参
* @param methodProxy 方法代理对象:CGLib生成的,用于调用父类方法
* @return 方法执行结果
* @throws Throwable 异常
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前置增强
System.out.println("CGLib动态代理 - 前置:校验" + method.getName() + "方法权限");
// 调用目标类(父类)的方法(非反射,性能更高)
Object result = methodProxy.invokeSuper(proxy, args);
// 后置增强
System.out.println("CGLib动态代理 - 后置:记录" + method.getName() + "方法日志");
return result;
}
}
测试
java
public class CglibDynamicProxyTest {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderService();
// 创建CGLib拦截器,传入目标对象
CglibDynamicProxyInterceptor interceptor = new CglibDynamicProxyInterceptor(target);
// 生成代理对象(目标类的子类)
OrderService proxy = (OrderService) interceptor.getProxyInstance();
// 调用代理对象方法:触发intercept方法
proxy.createOrder("ORDER_20251226");
}
}
运行结果

代码细节解释
(1)目标类OrderService
普通类,无任何接口,核心业务逻辑是createOrder方法。注意:该类和方法不能是final------CGLib是通过继承实现代理的,final类无法被继承,final方法无法被重写,会导致代理失败。这是CGLib的核心限制。
(2)方法拦截器CglibDynamicProxyInterceptor
-
private Object target;:用Object类型持有目标对象,适配所有普通类(无接口),提升拦截器的复用性。 -
构造器
public CglibDynamicProxyInterceptor(Object target):注入目标对象,建立拦截器与目标对象的关联,和前两种代理逻辑一致。 -
getProxyInstance():生成代理对象的入口,核心是Enhancer类(CGLib的"子类生成器"):-
enhancer.setSuperclass(target.getClass()):设置代理类的父类为目标类,这是CGLib的核心------代理类是目标类的子类,因此代理对象才能强转为目标类类型(如OrderService); -
enhancer.setCallback(this):设置回调拦截器,告诉代理类"增强逻辑在哪里",代理对象调用方法时会触发当前拦截器的intercept方法; -
enhancer.create():生成并返回代理对象(目标类的子类实例),CGLib会在运行时动态生成子类字节码并实例化。
-
-
核心方法
intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy):所有代理对象的方法调用都会触发此方法:-
proxy:动态生成的代理对象(目标类的子类实例,如OrderService$$EnhancerByCGLIB$$xxxx); -
method:被拦截的目标方法(父类OrderService的createOrder方法); -
args:目标方法的入参; -
methodProxy:CGLib生成的方法代理对象,用于调用父类(目标类)的方法; -
methodProxy.invokeSuper(proxy, args):核心!调用父类(目标类)的方法,这不是反射。CGLib在生成子类时,会直接生成调用父类方法的字节码,因此执行效率比JDK动态代理的反射更高; -
前置/后置增强:通用逻辑,可复用在所有代理的目标方法上。
-
(3)测试类CglibDynamicProxyTest
-
OrderService target = new OrderService();:创建目标对象(普通类,无接口); -
CglibDynamicProxyInterceptor interceptor = new CglibDynamicProxyInterceptor(target);:创建拦截器,传入目标对象; -
OrderService proxy = (OrderService) interceptor.getProxyInstance();:生成代理对象(目标类的子类实例),因此能强转为OrderService类型; -
proxy.createOrder("ORDER_20251226");:调用代理对象方法,触发intercept方法,执行增强逻辑和目标方法调用。
CGLib 动态代理的特点
- 优点:无需目标类实现接口,适用范围更广;直接生成字节码,调用方法无需反射,性能优于 JDK 动态代理;一个拦截器可适配多个普通类,灵活性高;
- 缺点:依赖第三方库(需引入CGLib);代理类继承目标类,受限于继承的特性(目标类 / 方法不能是 final);生成子类字节码,内存占用略高于 JDK 动态代理。
总结
三种代理方式的全面对比:

实际应用场景:
- Spring AOP:默认优先使用 JDK 动态代理(目标类有接口时),目标类无接口时自动切换为 CGLib;也可配置强制使用 CGLib。
- MyBatis:Mapper 接口的代理对象通过 JDK 动态代理生成,绑定 SQL 执行逻辑。
- 事务管理:Spring 的声明式事务通过代理模式为方法添加事务增强。
- 日志 / 监控:通过代理为方法统一添加日志打印、性能监控逻辑,无需侵入业务代码。
总结
- 静态代理适合简单、固定的业务场景,执行效率高但灵活性差;JDK 动态代理依赖接口和反射,适配多数有接口的代理场景;CGLib 通过继承实现,适合无接口的普通类代理,性能优于 JDK 动态代理。
- 三种代理方式的核心目的都是在不修改目标类源码的前提下增强方法逻辑,区别仅在于实现方式、性能和适用场景。
- 实际开发中(如 Spring),框架已封装了代理的底层实现,只需关注增强逻辑,无需手动实现代理细节,但理解三种代理的原理有助于排查问题和优化性能。