一、什么是代理?—— 从生活例子说起
想象你是一个明星,每天有很多人找你拍广告、接活动。但你不希望直接面对这些琐事,于是你找了一个 经纪人(代理) 帮你处理这些请求。
-
经纪人做什么?
-
过滤请求:比如拒绝不靠谱的广告。
-
增强功能:在接活动前先谈好价格,活动后结算费用。
-
隐藏细节:商家不知道你的电话号码,只能通过经纪人联系你。
-
代理的本质:在目标对象外面包一层,拦截对它的访问,并添加额外逻辑。
二、静态代理 vs 动态代理
1. 静态代理:手动写一个代理类
假设有一个 明星接口(Star)
:
public interface Star {
void acceptAdvertisement();
}
明星本人(目标对象):
public class RealStar implements Star {
@Override
public void acceptAdvertisement() {
System.out.println("明星本人接广告");
}
}
经纪人(静态代理类):
public class StaticProxy implements Star {
private Star target; // 被代理的明星
public StaticProxy(Star target) {
this.target = target;
}
@Override
public void acceptAdvertisement() {
System.out.println("经纪人谈价格");
target.acceptAdvertisement(); // 调用明星本人的方法
System.out.println("经纪人结算费用");
}
}
使用静态代理:
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = new StaticProxy(realStar);
proxy.acceptAdvertisement(); // 通过经纪人调用
}
缺点:
-
每个接口都要手动写一个代理类,代码冗余。
-
如果接口方法很多,代理类会变得臃肿。
2. 动态代理:运行时自动生成代理类
动态代理不需要手动写代理类,而是在 运行时 动态生成代理对象。
Java 提供了两种实现方式:
-
JDK 动态代理:基于接口(要求目标对象必须实现接口)
-
CGLIB 动态代理:基于继承(可代理普通类)
三、JDK 动态代理详解
1. 核心类和接口
-
java.lang.reflect.Proxy
:生成代理对象的工具类。 -
java.lang.reflect.InvocationHandler
:代理逻辑处理器。
2. 代码示例
目标接口和实现类(同上):
public interface Star {
void acceptAdvertisement();
}
public class RealStar implements Star {
@Override
public void acceptAdvertisement() {
System.out.println("明星本人接广告");
}
}
动态代理处理器:
public class StarHandler implements InvocationHandler {
private Object target; // 被代理的对象
public StarHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人谈价格");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("经纪人结算费用");
return result;
}
}
生成动态代理对象:
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = (Star) Proxy.newProxyInstance(
realStar.getClass().getClassLoader(), // 类加载器
realStar.getClass().getInterfaces(), // 目标接口
new StarHandler(realStar) // 代理逻辑处理器
);
proxy.acceptAdvertisement(); // 输出:经纪人谈价格 → 明星本人接广告 → 经纪人结算费用
}
3. JDK 动态代理原理
-
运行时生成代理类:
Proxy.newProxyInstance()
会动态生成一个类,名字类似$Proxy0
。 -
代理类实现目标接口:代理类会实现
Star
接口,并在每个方法中调用InvocationHandler.invoke()
。 -
隐藏细节:开发者无需关心代理类的具体实现。
四、CGLIB 动态代理
如果目标类没有实现接口,可以使用 CGLIB(Code Generation Library)动态代理。它通过继承目标类生成子类来实现代理。
1. 代码示例
目标类(无接口):
public class RealStar {
public void acceptAdvertisement() {
System.out.println("明星本人接广告");
}
}
CGLIB 代理处理器:
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("经纪人谈价格");
Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法
System.out.println("经纪人结算费用");
return result;
}
}
生成代理对象:
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealStar.class); // 设置目标类
enhancer.setCallback(new CglibProxy()); // 设置代理逻辑
RealStar proxy = (RealStar) enhancer.create(); // 创建代理对象
proxy.acceptAdvertisement(); // 输出同上
}
2. JDK vs CGLIB 对比
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
目标对象要求 | 必须实现接口 | 可以代理普通类 |
性能 | 生成代理较快,执行稍慢 | 生成代理较慢,执行较快 |
原理 | 基于接口实现 | 基于继承生成子类 |
Spring 默认选择 | 目标有接口时优先使用 | 目标无接口时使用 |
五、动态代理的应用场景
1. AOP(面向切面编程)
-
日志记录:在方法执行前后打印日志。
-
事务管理:在方法执行前开启事务,执行后提交或回滚。
-
权限校验:在方法执行前检查用户权限。
示例:Spring 的 @Transactional
事务注解就是通过动态代理实现的。
2. RPC 框架
远程调用(如 Dubbo、gRPC)中,客户端通过动态代理生成一个“假的”本地对象,调用该对象的方法时,实际会通过网络发送请求到服务端。
示例:
// 用户以为调用的是本地方法,实际是通过代理发送网络请求
UserService userService = proxy.create(UserService.class);
User user = userService.getUserById(1);
3. Mock 测试
在单元测试中,用动态代理生成一个虚拟对象,模拟真实接口的行为。
示例:
List<String> mockList = mock(List.class);
when(mockList.get(0)).thenReturn("test");
System.out.println(mockList.get(0)); // 输出 "test"
4. 延迟加载(Lazy Loading)
在 Hibernate 中,关联对象(如 User
的 Order
列表)可以通过动态代理实现延迟加载,只有实际访问时才会查询数据库。
六、动态代理的本质
动态代理的核心是 在运行时动态生成一个类,拦截对目标方法的调用,并插入额外逻辑。它解决了以下问题:
-
代码复用:避免为每个类编写重复的代理代码。
-
解耦:将通用逻辑(如日志、事务)与业务逻辑分离。
-
灵活性:动态代理可以应对接口或类的变化。
七、实际工作中的应用举例
假设你要为所有 Service 层的方法添加执行时间统计:
1. 定义切面(使用动态代理)
@Aspect
@Component
public class TimeAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 调用原方法
long time = System.currentTimeMillis() - start;
System.out.println("方法执行耗时:" + time + "ms");
return result;
}
}
其中动态代理的体现:
1.一、动态代理的核心体现
1. 代理对象的生成
-
原始对象:
com.example.service
包下的类(如UserService
)会被 Spring 容器初始化为普通 Bean。 -
代理对象:Spring 检测到
@Aspect
切面后,会为这些 Bean 动态生成代理对象,替换原始 Bean。-
如果目标类实现了接口 → 使用 JDK 动态代理(生成一个实现相同接口的代理类)。
-
如果目标类无接口 → 使用 CGLIB 动态代理(生成目标类的子类)。
-
2. 方法调用的拦截
当调用 UserService
的方法时,实际调用的是 代理对象的方法,代理对象会:
-
拦截方法调用:代理对象检测到方法匹配切入点表达式(
execution(* com.example.service.*.*(..))
)。 -
执行切面逻辑:调用你的
logTime()
方法,在其中插入耗时统计代码。 -
转发到原始方法:通过
joinPoint.proceed()
调用原始目标方法。
3. 整个过程示意图
开发者调用 userService.updateUser()
→ 实际调用的是代理对象的方法
→ 代理对象触发 TimeAspect.logTime()
→ logTime() 中调用 joinPoint.proceed()
→ 代理对象调用原始 userService.updateUser()
→ 返回结果,继续执行 logTime() 的剩余逻辑。
1.二、具体代码中的动态代理痕迹
1. 代理对象的类名
运行程序时,可以看到 Spring 生成的代理类名:
-
JDK 动态代理:类名类似
com.example.service.UserService$$EnhancerBySpringCGLIB$$12345678
。 -
CGLIB 动态代理:类名类似
$Proxy0
(JDK)或UserService$$EnhancerBySpringCGLIB$$...
。
2. 调试观察代理对象
在调试模式下,查看 UserService
实例的类名:
@Autowired
private UserService userService;
public void someMethod() {
System.out.println(userService.getClass().getName());
// 输出可能是 UserService$$EnhancerBySpringCGLIB$$...
}
1.三、动态代理的关键优势
1. 无需侵入目标代码
-
你不需要修改
UserService
或其他 Service 类的代码,只需定义一个切面类。 -
动态代理自动将增强逻辑“织入”目标方法。
2. 统一管理横切关注点
-
日志、事务、权限等通用逻辑通过切面集中处理,避免代码重复。
3. 灵活应对变化
-
修改切面逻辑(如调整日志格式)只需修改
TimeAspect
,无需改动业务代码。
1.四、对比静态代理理解动态代理
假设不用动态代理,手动实现类似功能:
1. 静态代理代码
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void updateUser(User user) {
long start = System.currentTimeMillis();
target.updateUser(user); // 调用原始方法
long time = System.currentTimeMillis() - start;
System.out.println("方法执行耗时:" + time + "ms");
}
// 需要为每个方法手动重复耗时统计逻辑!
}
2. 动态代理的优势
-
无需手动编写代理类:动态代理自动生成。
-
通用性:一个切面可作用于多个类和方法。
-
维护性:逻辑集中,修改方便。
1.五、总结
-
Spring 自动为
com.example.service
下的 Bean 生成代理对象。 -
代理对象拦截方法调用,插入
logTime()
中的耗时统计逻辑。 -
开发者无需感知代理的存在,只需关注切面逻辑和目标业务代码。
2. Spring 的幕后操作
-
Spring 使用动态代理(JDK 或 CGLIB)生成 Service 的代理对象。
-
当调用
userService.updateUser()
时,实际调用的是代理对象的方法,代理会先执行logTime()
中的逻辑。
八、总结
-
动态代理:在运行时生成代理类,拦截方法调用并添加额外逻辑。
-
JDK 动态代理:基于接口,要求目标类必须实现接口。
-
CGLIB:基于继承,可代理普通类。
-
应用场景:AOP、RPC、Mock 测试、延迟加载等。
关键记忆点:
动态代理 = 运行时生成“中介”类 + 拦截方法调用 + 插入增强逻辑。