Java代理模式详解:静态代理与动态代理实战

发布时间:2026/9/10 17:40:47
Java代理模式详解:静态代理与动态代理实战 1. 代理模式在Java中的核心价值代理模式是Java开发者必须掌握的核心设计模式之一它通过引入代理对象来控制对原始对象的访问。这种模式在日常开发中有着广泛的应用场景特别是在需要控制对象访问权限、添加额外功能或延迟加载资源的情况下。我在实际项目中遇到过这样一个典型场景系统需要记录所有数据库查询的执行时间但又不希望在每个DAO方法中都手动添加计时逻辑。这时候代理模式就派上了大用场。通过动态代理我们可以在不修改原有代码的情况下统一为所有数据库操作添加性能监控功能。重要提示理解代理模式的关键在于区分代理对象和被代理对象。代理对象持有被代理对象的引用并在调用前后执行额外操作但最终的业务逻辑还是由被代理对象完成。Java中的代理主要分为两种实现方式静态代理在编译期就确定代理关系需要为每个被代理类创建对应的代理类动态代理在运行时动态生成代理类更加灵活高效2. 静态代理的实现与局限2.1 静态代理的基本实现静态代理是最基础的代理形式我们需要手动创建代理类。下面是一个典型的静态代理示例// 接口定义 public interface UserService { void addUser(String username); } // 被代理类 public class UserServiceImpl implements UserService { Override public void addUser(String username) { System.out.println(添加用户: username); } } // 代理类 public class UserServiceProxy implements UserService { private UserService target; // 持有被代理对象 public UserServiceProxy(UserService target) { this.target target; } Override public void addUser(String username) { System.out.println(前置处理: 验证用户权限); target.addUser(username); // 调用被代理对象的方法 System.out.println(后置处理: 记录操作日志); } }使用方式UserService service new UserServiceProxy(new UserServiceImpl()); service.addUser(张三);2.2 静态代理的优缺点分析静态代理的主要优点在于实现简单直观容易理解可以在不修改目标对象的前提下扩展功能代理类和被代理类都实现相同接口符合开闭原则但静态代理也存在明显缺点代理类和被代理类是一对一的关系如果有多个类需要代理就需要创建多个代理类接口增加方法时代理类和被代理类都需要修改维护成本高代码冗余每个代理类都需要重复编写类似的代理逻辑在实际项目中静态代理通常只适用于代理少量固定类的情况。对于需要代理多个类或者代理关系可能变化的场景动态代理是更好的选择。3. Java动态代理深度解析3.1 JDK动态代理实现原理JDK动态代理是Java标准库提供的代理机制它利用反射API在运行时动态生成代理类。下面是典型实现public class LoggingInvocationHandler implements InvocationHandler { private Object target; // 被代理对象 public LoggingInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(调用方法: method.getName()); long start System.currentTimeMillis(); Object result method.invoke(target, args); // 调用被代理对象的方法 long duration System.currentTimeMillis() - start; System.out.println(方法执行耗时: duration ms); return result; } }使用方式UserService userService new UserServiceImpl(); UserService proxy (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new LoggingInvocationHandler(userService) ); proxy.addUser(李四);3.2 JDK动态代理的核心机制JDK动态代理的实现基于以下几个关键点Proxy类提供创建动态代理的静态方法InvocationHandler接口代理逻辑的具体实现运行时生成的代理类继承Proxy并实现指定接口JDK动态代理有一些重要特性需要注意只能代理接口不能代理类代理类会覆盖Object类的equals、hashCode和toString方法代理类的类名通常以$Proxy开头代理实例的性能比普通类实例稍差因为涉及反射调用我在实际项目中曾遇到一个性能问题在高频调用的场景下动态代理的方法调用比直接调用慢3-5倍。解决方案是尽量减少代理方法中的反射调用或者对热点方法进行缓存优化。4. CGLIB动态代理技术4.1 CGLIB与JDK动态代理的对比当我们需要代理没有实现接口的类时JDK动态代理就无法满足需求了。这时可以使用CGLIB(Code Generation Library)特性JDK动态代理CGLIB动态代理代理对象必须实现接口可以代理普通类性能中等较快实现方式反射字节码生成依赖JDK自带需要额外jar包方法过滤只能代理接口方法可以过滤特定方法4.2 CGLIB实现示例下面是使用CGLIB创建代理的典型代码public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target target; Enhancer enhancer new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(CGLIB前置处理); Object result proxy.invokeSuper(obj, args); System.out.println(CGLIB后置处理); return result; } }使用方式UserService userService (UserService) new CglibProxy().getInstance(new UserServiceImpl()); userService.addUser(王五);CGLIB通过生成目标类的子类来实现代理因此需要注意被代理的类和方法不能是final的构造函数会被调用两次CGLIB的实现机制导致比JDK动态代理更消耗内存5. 代理模式的高级应用场景5.1 Spring AOP中的代理实现Spring框架广泛使用代理模式来实现AOP(面向切面编程)。根据目标对象是否实现接口Spring会自动选择JDK动态代理或CGLIBConfiguration EnableAspectJAutoProxy(proxyTargetClass true) // 强制使用CGLIB public class AppConfig { // 配置类 }Spring AOP的代理机制有几个特点默认对接口使用JDK动态代理对类使用CGLIB可以通过配置强制使用CGLIB代理对象由Spring容器管理开发者通常无感知5.2 MyBatis中的动态代理MyBatis的Mapper接口也是通过JDK动态代理实现的public class MapperProxyT implements InvocationHandler { private final SqlSession sqlSession; private final ClassT mapperInterface; public MapperProxy(SqlSession sqlSession, ClassT mapperInterface) { this.sqlSession sqlSession; this.mapperInterface mapperInterface; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 将方法调用转换为SQL执行 return sqlSession.selectOne(method.getName(), args[0]); } }这种设计使得MyBatis的Mapper接口不需要实现类极大简化了DAO层的开发。5.3 RPC框架中的代理应用在分布式系统中RPC框架通常使用动态代理来隐藏远程调用的细节public class RpcClientProxy implements InvocationHandler { private String host; private int port; public RpcClientProxy(String host, int port) { this.host host; this.port port; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 构造请求 RpcRequest request new RpcRequest(); request.setClassName(method.getDeclaringClass().getName()); request.setMethodName(method.getName()); request.setParameters(args); // 发送网络请求 return sendRequest(host, port, request); } }这种设计使得客户端可以像调用本地方法一样调用远程服务大大简化了分布式编程。6. 代理模式的性能优化6.1 代理对象缓存由于创建代理对象的开销较大通常应该缓存代理实例public class ProxyFactory { private static final MapClass?, Object proxyCache new ConcurrentHashMap(); public static T T getProxy(ClassT interfaceClass) { return (T) proxyCache.computeIfAbsent(interfaceClass, clazz - { InvocationHandler handler new CustomInvocationHandler(); return Proxy.newProxyInstance( clazz.getClassLoader(), new Class[]{clazz}, handler ); }); } }6.2 方法调用优化对于高频调用的代理方法可以考虑以下优化手段缓存Method对象避免重复获取对简单方法直接调用不经过反射使用MethodHandle代替反射APIpublic class OptimizedInvocationHandler implements InvocationHandler { private final Object target; private final MapMethod, MethodHandle methodHandleCache new ConcurrentHashMap(); public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodHandle handle methodHandleCache.computeIfAbsent(method, m - { try { MethodHandles.Lookup lookup MethodHandles.lookup(); return lookup.unreflect(method); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); return handle.invokeWithArguments(args); } }6.3 选择性代理不是所有方法都需要代理可以通过注解或配置指定需要代理的方法Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface ProxyMethod { } public class SelectiveInvocationHandler implements InvocationHandler { Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.isAnnotationPresent(ProxyMethod.class)) { // 执行代理逻辑 System.out.println(代理方法: method.getName()); } return method.invoke(target, args); } }7. 常见问题与解决方案7.1 代理对象无法序列化问题当我们需要序列化代理对象时可能会遇到问题解决方案是public class SerializableInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID 1L; private Object target; // 实现writeReplace方法 protected Object writeReplace() { return Proxy.newProxyInstance( getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } }7.2 自调用问题在代理对象的方法中调用自身的另一个代理方法时第二个调用不会经过代理public interface Service { void methodA(); void methodB(); } public class ServiceImpl implements Service { Override public void methodA() { methodB(); // 这个调用不会经过代理 } Override public void methodB() { // 实现 } }解决方案通过AopContext获取当前代理对象将代理对象作为参数传入重构代码避免自调用7.3 代理对象识别有时需要判断一个对象是否是代理对象public static boolean isProxy(Object obj) { if (obj null) { return false; } // JDK动态代理 if (Proxy.isProxyClass(obj.getClass())) { return true; } // CGLIB代理 return obj.getClass().getName().contains($$EnhancerByCGLIB$$); }8. 代理模式的最佳实践根据我多年的项目经验以下是使用代理模式的一些建议接口设计原则为需要代理的类定义清晰的接口保持接口单一职责代理粒度控制不要过度使用代理只为真正需要增强或控制的方法添加代理性能考量在高性能场景下考虑使用静态代理或手动编码代替动态代理异常处理在InvocationHandler中妥善处理异常避免吞没原始异常线程安全确保InvocationHandler的实现是线程安全的一个典型的综合应用案例是数据库事务管理public class TransactionInvocationHandler implements InvocationHandler { private Object target; private DataSource dataSource; Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Connection conn null; try { conn dataSource.getConnection(); conn.setAutoCommit(false); Object result method.invoke(target, args); conn.commit(); return result; } catch (Exception e) { if (conn ! null) { conn.rollback(); } throw e; } finally { if (conn ! null) { conn.close(); } } } }这种设计可以统一管理事务而不需要在每个业务方法中重复编写事务代码。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询