Java中的代理模式

在Java中,代理模式用于为对象提供一种间接访问方式,常用于增强功能(如日志、事务等)。代理分为静态代理动态代理,两者实现方式和原理不同。


一、静态代理

实现过程

  1. 定义公共接口

    代理类和被代理类需实现同一接口。

    java

    csharp 复制代码
    public interface UserService {
        void save();
    }
  2. 实现被代理类(真实对象)

    java

    typescript 复制代码
    public class UserServiceImpl implements UserService {
        @Override
        public void save() {
            System.out.println("保存用户数据");
        }
    }
  3. 实现代理类

    代理类持有被代理对象的引用,并在方法调用前后添加增强逻辑。

    java

    csharp 复制代码
    public class UserServiceProxy implements UserService {
        private final UserService target;  // 持有真实对象
    
        public UserServiceProxy(UserService target) {
            this.target = target;
        }
    
        @Override
        public void save() {
            System.out.println("前置增强(如日志)");
            target.save();  // 调用真实对象的方法
            System.out.println("后置增强(如事务提交)");
        }
    }
  4. 使用代理

    java

    java 复制代码
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(realService);
        proxy.save();  // 通过代理调用方法
    }

原理

  • 编译时绑定:代理类在编译期已确定,需手动编写代码。
  • 直接调用:代理类通过组合持有真实对象,显式调用其方法。
  • 缺点:每个被代理类需单独编写代理类,代码冗余。

二、动态代理

实现过程(基于JDK内置Proxy类)

  1. 定义接口(同上)

    java

    csharp 复制代码
    public interface UserService {
        void save();
    }
  2. 实现被代理类(同上)

    java

    java 复制代码
    public class UserServiceImpl implements UserService { ... }
  3. 实现InvocationHandler

    定义代理逻辑的通用处理器。

    java

    typescript 复制代码
    public class LogHandler implements InvocationHandler {
        private final Object target;  // 持有任意真实对象
    
        public LogHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("前置增强:" + method.getName());
            Object result = method.invoke(target, args);  // 反射调用真实对象方法
            System.out.println("后置增强");
            return result;
        }
    }
  4. 动态生成代理对象

    使用Proxy.newProxyInstance创建代理实例。

    java

    scss 复制代码
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        
        // 动态生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            realService.getClass().getClassLoader(),  // 类加载器
            realService.getClass().getInterfaces(),   // 实现的接口
            new LogHandler(realService)               // InvocationHandler
        );
    
        proxy.save();  // 调用代理方法
    }

原理

  1. 运行时动态生成代理类

    • JDK的Proxy类在运行时生成代理类的字节码(类名通常为$Proxy0)。
    • 通过sun.misc.ProxyGenerator生成字节码,并加载到JVM。
  2. 代理类结构

    生成的代理类继承Proxy并实现目标接口:

    java

    scala 复制代码
    public final class $Proxy0 extends Proxy implements UserService {
        public $Proxy0(InvocationHandler h) {
            super(h);
        }
    
        @Override
        public void save() {
            super.h.invoke(this,  // 调用InvocationHandler的invoke方法
                UserService.class.getMethod("save"), 
                null);
        }
    }

关键点

  • JDK动态代理限制 :只能代理接口 ,不能代理类(因Java单继承,代理类已继承Proxy)。
  • 性能开销:反射调用方法比直接调用略慢(但现代JVM已优化)。
  • 替代方案 :若需代理类而非接口,可用CGLIB(通过继承实现代理)。

三、对比总结

特性 静态代理 动态代理(JDK)
实现时机 编译期(手动编写代理类) 运行期(自动生成代理类)
代码冗余 每个被代理类需单独编写代理类 通用(一个处理器处理多个类)
灵活性 低(修改需重新编译) 高(动态配置)
代理对象类型 类或接口 仅接口
性能 无反射开销(直接调用) 有反射开销(但可接受)
典型应用 简单场景、少量代理 Spring AOP、RPC框架等

四、动态代理底层原理(字节码层面)

  1. 生成代理类字节码
    Proxy.newProxyInstance()调用ProxyGenerator.generateProxyClass()生成字节码(可通过-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true导出.class文件)。
  2. 加载代理类
    通过ClassLoader将字节码加载到JVM。
  3. 实例化代理对象
    利用反射创建代理实例,构造器传入InvocationHandler
  4. 方法调用转发
    代理类的方法调用全部委托给InvocationHandler.invoke(),由它决定增强逻辑和真实对象的调用。

五、扩展:CGLIB动态代理

若需代理(非接口),可用CGLIB库:

java

typescript 复制代码
// 1. 实现MethodInterceptor
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;
    }
}

// 2. 生成代理对象
public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(UserServiceImpl.class);  // 设置被代理类
    enhancer.setCallback(new CglibProxy());        // 设置回调
    UserService proxy = (UserService) enhancer.create();  // 创建代理
    proxy.save();
}

原理:通过ASM库生成被代理类的子类,重写方法并插入增强逻辑。


通过理解静态代理与动态代理的实现和原理,可灵活选择适合场景的代理方式,高效实现功能增强和解耦。

相关推荐
2401_894915532 小时前
GEO 优化源码全解析:从搜索引擎到 AI 引擎的底层改写逻辑
java·服务器·前端·数据库·人工智能·分布式·搜索引擎
Java牛马5 小时前
SpringBoot Starter 依赖相关总结
java·spring·springboot·starter·自动装配·依赖
吴声子夜歌6 小时前
Java面试——Spring Cloud原理及应用(二)
java·spring cloud·面试
SomeB1oody6 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
东小西6 小时前
【SAA实战】第 1 篇:ReactAgent 入门——先撸个"会调工具的助手"跑起来
java·人工智能·spring
黄敬峰7 小时前
一文搞懂 Docker + Dockerfile:从镜像构建到全栈部署
面试
Profile排查笔记7 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
用户938515635077 小时前
用 AI 结对编程从 0 搭一个"单词后台管理系统":Next.js + Supabase + Drizzle + shadcn/ui 全记录
后端·postgresql·next.js
mqiqe7 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui
脉动数据行情7 小时前
Java 实现台股 TWSE/TPEx 行情采集(个股 + K 线)
java·开发语言·twse·tpex·台股