每日面试题22:静态代理和动态代理的区别
静态代理 vs 动态代理:从原理到实践的深度对比
在面向对象编程中,代理模式(Proxy Pattern)是一种经典的行为型设计模式,其核心思想是通过一个“代理对象”控制对真实对象的访问,在不修改真实对象的前提下,为其添加额外功能(如日志记录、事务管理、权限校验等)。根据代理类的生成时机,代理模式可分为静态代理和动态代理两大类。本文将从实现原理、代码示例、优缺点及适用场景等维度,系统对比两者的差异。
一、代理模式的核心价值
在展开对比前,我们先明确代理模式解决的问题:
假设我们需要为一个业务类(如UserService
)添加日志记录功能,最直接的方式是修改UserService
的代码,在每个方法前后插入日志。但这样会违反开闭原则(对扩展开放,对修改关闭),尤其是当需要为多个类添加相同功能时,重复代码会急剧增加,维护成本极高。
代理模式通过引入“代理对象”,将额外功能逻辑封装在代理中,真实对象仅负责核心业务,实现了功能解耦和代码复用。
二、静态代理:编译期确定的“固定助手”
静态代理是最基础的代理实现方式,其代理类在编译期手动编写(或通过工具生成),并在运行时直接使用。
1. 实现步骤
静态代理的核心是:代理类与被代理类实现相同接口,代理类持有被代理对象的引用,并在重写的接口方法中调用被代理对象的方法,同时添加扩展逻辑。
以用户服务(UserService
)的日志代理为例:
步骤1:定义业务接口
public interface UserService {void addUser(String username);void deleteUser(Long userId);
}
步骤2:实现真实业务类(被代理对象)
public class UserServiceImpl implements UserService {@Overridepublic void addUser(String username) {System.out.println("添加用户:" + username);}@Overridepublic void deleteUser(Long userId) {System.out.println("删除用户,ID:" + userId);}
}
步骤3:编写静态代理类
代理类需实现UserService
接口,持有UserServiceImpl
实例,并在方法中嵌入日志逻辑:
public class UserServiceStaticProxy implements UserService {// 持有被代理对象的引用private final UserService target;public UserServiceStaticProxy(UserService target) {this.target = target;}@Overridepublic void addUser(String username) {// 前置增强:记录方法调用前的信息System.out.println("[日志] 开始执行addUser,参数:" + username);// 调用真实对象的方法target.addUser(username);// 后置增强:记录方法执行结果System.out.println("[日志] addUser执行完成");}@Overridepublic void deleteUser(Long userId) {System.out.println("[日志] 开始执行deleteUser,参数:" + userId);target.deleteUser(userId);System.out.println("[日志] deleteUser执行完成");}
}
步骤4:使用代理对象
public class Client {public static void main(String[] args) {// 创建真实对象UserService realService = new UserServiceImpl();// 创建代理对象,传入真实对象UserService proxyService = new UserServiceStaticProxy(realService);// 通过代理对象调用方法(自动触发日志)proxyService.addUser("张三");proxyService.deleteUser(1L);}
}
2. 静态代理的特点
优点:
- 实现简单,逻辑清晰,适合新手理解代理模式;
- 编译期生成代理类,运行时无额外性能损耗(反射调用比直接调用慢,但静态代理无反射)。
缺点:
- 强耦合:代理类与被代理类必须实现相同接口,若接口新增方法,代理类需同步修改;
- 可维护性差:每个被代理类都需要单独编写代理类(如
OrderService
需OrderServiceStaticProxy
),代码冗余; - 扩展性低:无法动态切换被代理对象或动态添加新的增强逻辑。
三、动态代理:运行时生成的“万能助手”
动态代理的核心是代理类在运行时动态生成,无需手动编写。它通过反射机制拦截方法调用,并在运行时将增强逻辑织入目标方法。Java中主流的动态代理实现有两种:JDK动态代理和CGLIB动态代理。
1. JDK动态代理:基于接口的“轻量级代理”
JDK动态代理是Java标准库提供的内置代理机制(java.lang.reflect.Proxy
),其原理是通过反射生成一个实现目标接口的代理类,并将方法调用转发到InvocationHandler
处理器中。
核心组件
InvocationHandler
:代理对象的方法调用处理器,所有通过代理对象发起的方法调用都会被转发到此处;Proxy
:用于生成代理类实例的工具类。
实现步骤(以UserService
日志代理为例)
步骤1:定义业务接口(同静态代理)
public interface UserService {void addUser(String username);void deleteUser(Long userId);
}
步骤2:实现真实业务类(同静态代理)
public class UserServiceImpl implements UserService { ... }
步骤3:编写InvocationHandler
InvocationHandler
的invoke
方法是核心,负责拦截所有代理对象的方法调用,并添加增强逻辑:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class LogInvocationHandler implements InvocationHandler {// 持有被代理对象的引用private final Object target;public LogInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强:记录方法调用信息String methodName = method.getName();System.out.println("[日志] 开始执行" + methodName + ",参数:" + String.join(",", toString(args)));// 调用真实对象的方法(核心逻辑)Object result = method.invoke(target, args);// 后置增强:记录方法执行完成System.out.println("[日志] " + methodName + "执行完成");return result;}// 辅助方法:将参数数组转为字符串private String toString(Object[] args) {if (args == null) return "无";StringBuilder sb = new StringBuilder();for (int i = 0; i < args.length; i++) {sb.append(args[i]);if (i != args.length - 1) sb.append(",");}return sb.toString();}
}
步骤4:生成代理对象并使用
通过Proxy.newProxyInstance
方法动态生成代理类实例:
import java.lang.reflect.Proxy;public class Client {public static void main(String[] args) {// 真实对象UserService realService = new UserServiceImpl();// 创建InvocationHandler,绑定真实对象InvocationHandler handler = new LogInvocationHandler(realService);// 动态生成代理对象(类型为UserService)UserService proxyService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), // 类加载器new Class<?>[]{UserService.class}, // 代理需要实现的接口handler // 方法调用处理器);// 通过代理对象调用方法(自动触发日志)proxyService.addUser("李四");proxyService.deleteUser(2L);}
}
输出结果
[日志] 开始执行addUser,参数:李四
添加用户:李四
[日志] addUser执行完成
[日志] 开始执行deleteUser,参数:2
删除用户,ID:2
[日志] deleteUser执行完成
2. CGLIB动态代理:基于继承的“全能代理”
JDK动态代理的局限性在于只能代理接口,若目标类未实现接口(如直接定义的class
),则无法使用。此时可使用CGLIB(Code Generation Library),它通过继承目标类生成子类作为代理,从而实现对无接口类的代理。
核心依赖
需引入CGLIB库(Maven坐标:cglib:cglib:3.3.0
)。
实现步骤(以无接口的OrderService
为例)
步骤1:定义无接口的真实类
public class OrderService {public void createOrder(Long userId, String product) {System.out.println("创建订单:用户" + userId + "购买" + product);}
}
步骤2:编写MethodInterceptor(方法拦截器)
CGLIB通过MethodInterceptor
拦截所有方法调用:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 前置增强System.out.println("[CGLIB日志] 开始执行" + method.getName() + ",参数:" + String.join(",", toString(args)));// 调用真实对象的方法(通过MethodProxy,避免递归调用)Object result = proxy.invokeSuper(obj, args);// 后置增强System.out.println("[CGLIB日志] " + method.getName() + "执行完成");return result;}private String toString(Object[] args) {if (args == null) return "无";StringBuilder sb = new StringBuilder();for (int i = 0; i < args.length; i++) {sb.append(args[i]);if (i != args.length - 1) sb.append(",");}return sb.toString();}
}
步骤3:生成代理对象并使用
通过Enhancer
类生成代理类:
import net.sf.cglib.proxy.Enhancer;public class Client {public static void main(String[] args) {// 真实对象OrderService realService = new OrderService();// 创建Enhancer,设置父类(目标类)Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OrderService.class);// 设置方法拦截器enhancer.setCallback(new LogMethodInterceptor());// 生成代理对象(类型为OrderService的子类)OrderService proxyService = (OrderService) enhancer.create();// 调用代理方法(触发日志)proxyService.createOrder(100L, "手机");}
}
输出结果
[CGLIB日志] 开始执行createOrder,参数:100,手机
创建订单:用户100购买手机
[CGLIB日志] createOrder执行完成
3. 动态代理的对比与选择
特性 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
代理方式 | 基于接口,生成接口的实现类 | 基于继承,生成目标类的子类 |
目标类要求 | 必须实现至少一个接口 | 可以是任意类(包括无接口的类) |
性能 | 反射调用,略慢于CGLIB(JDK8+后差距缩小) | 基于继承,方法调用更快(但生成子类耗时) |
适用场景 | 接口稳定的业务类(如Spring的Service) | 无接口的类(如工具类、第三方库类) |
扩展限制 | 无法代理接口中未声明的方法 | 可以代理目标类的所有方法(包括私有方法?需注意访问权限) |
四、静态代理 vs 动态代理:核心差异总结
维度 | 静态代理 | 动态代理 |
---|---|---|
代理类生成时机 | 编译期(手动/工具生成) | 运行期(动态生成字节码) |
代码侵入性 | 需为每个被代理类编写代理类 | 无需手动编写代理类,通用性强 |
维护成本 | 高(接口变更需同步修改代理类) | 低(仅需调整InvocationHandler) |
扩展性 | 差(无法动态代理新类) | 强(一个代理可代理多个类/接口) |
适用场景 | 接口稳定、代理逻辑简单的场景 | 接口多变、需要批量代理或AOP的场景 |
五、实际应用场景建议
- 静态代理:适合小型项目或代理逻辑非常固定的场景(如单个类的日志/校验),优点是代码直观,易于调试。
- JDK动态代理:适合基于接口的Spring Service层(90%的Java业务系统采用接口设计),配合Spring AOP实现事务管理、权限控制等功能。
- CGLIB动态代理:适合代理无接口的类(如MyBatis的Mapper接口本质是接口,仍用JDK代理;但某些工具类可能未实现接口),或需要对类的所有方法(包括私有方法)进行代理的场景(需注意CGLIB通过继承实现,私有方法无法被重写,实际仍调用父类方法)。
总结
静态代理是代理模式的“入门版”,通过手动编写代理类实现功能扩展,适合简单场景;动态代理则是“进阶版”,通过反射或字节码生成技术,在运行时动态创建代理类,解决了静态代理的代码冗余和维护问题,是框架(如Spring AOP)的核心实现技术。理解两者的差异,有助于在实际开发中根据需求选择合适的代理方式,提升代码的可维护性和扩展性。