ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

代理模式详解:控制对象访问的利器(静态代理 + JDK 动态代理)

代理模式详解:控制对象访问的利器(静态代理 + JDK 动态代理) 一、代理模式定义Proxy 模式是一种结构型模式。它为目标对象提供一个代理对象由代理对象控制对目标对象的访问。代理与目标实现相同的接口客户端只与代理打交道代理在调用目标前后可执行额外操作而不是单纯把请求转发出去。一句话想给某个对象加前置/后置处理鉴权、日志、缓存、事务又不想改它的业务代码就套一层代理。二、为什么需要代理模式动机访问控制目标对象不该直接暴露如远程服务、受限资源代理先做权限校验再决定是否放行。功能增强在目标方法前后统一织入日志、监控、事务等横切逻辑避免侵入业务代码。解耦客户端客户端依赖的是抽象接口目标对象可随时替换代理起到缓冲层作用。三、代理模式的结构角色与职责角色职责抽象主题Subject真实主题与代理主题的共同接口客户端面向它编程真实主题RealSubject被代理的目标对象真正执行业务逻辑代理Proxy持有 RealSubject 引用在转发请求前后执行额外操作持有«interface»SubjectsailBook()RealSubjectsailBook()ProxySubject-realSubject: RealSubjectsailBook()四、代码实现静态代理4.1 抽象主题与真实主题// 抽象主题publicinterfaceSubject{voidsailBook();}// 真实主题真正卖书的逻辑publicclassRealSubjectimplementsSubject{OverridepublicvoidsailBook(){System.out.println(卖书);}}4.2 静态代理publicclassProxySubjectimplementsSubject{privateRealSubjectrealSubject;// 持有真实对象publicProxySubject(RealSubjectrealSubject){this.realSubjectrealSubject;}OverridepublicvoidsailBook(){dazhe();// 调用前增强逻辑realSubject.sailBook();// 转发给真实对象give();// 调用后增强逻辑}privatevoiddazhe(){System.out.println(打折);}privatevoidgive(){System.out.println(代金卷);}}4.3 客户端publicclassMainClass{publicstaticvoidmain(String[]args){RealSubjectrealnewRealSubject();// 不用代理real.sailBook();// 使用代理客户端只面向 Subject不感知代理内部ProxySubjectproxynewProxySubject(real);proxy.sailBook();}}运行结果卖书 打折 卖书 代金卷关键点客户端面向Subject接口调用增强逻辑打折/代金券被收口在代理里真实主题RealSubject一行没改。五、静态代理的痛点为什么需要动态代理静态代理能跑但有硬伤代理类会膨胀接口里每多一个方法如buyBook、returnBook代理类都得手写一遍转发逻辑方法一多代理类变得又长又重复。增强逻辑无法复用打折/代金券这类增强写死在ProxySubject里换一个目标对象就要重写一个代理类。接口一改全改抽象主题增删方法真实类和代理类都得同步改。这些痛点催生了运行时由 JVM 自动生成代理类的动态代理。六、动态代理JDKJDK 动态代理靠三个要素协作要素作用接口Subject被代理的目标必须实现接口JDK 动态代理只代理接口InvocationHandler实现invoke(proxy, method, args)在真正调用前后织入增强逻辑Proxy.newProxyInstance(classLoader, interfaces, handler)运行时由 JVM 生成代理类字节码并返回实例6.1 通用处理器改进版笔记里的MyHandler直接持有RealSubject只能代理卖书这一种目标。实际应持有通用的Object target这样同一套增强可复用在任意实现了接口的对象上importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;publicclassMyHandlerimplementsInvocationHandler{privateObjecttarget;// 通用目标而非写死 RealSubjectpublicvoidsetTarget(Objecttarget){this.targettarget;}// proxy: 代理对象; method: 被调用的方法; args: 方法参数OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{dazhe();// 反射调用真实对象的方法Objectresultmethod.invoke(target,args);give();returnresult;}privatevoiddazhe(){System.out.println(打折);}privatevoidgive(){System.out.println(代金卷);}}6.2 客户端importjava.lang.reflect.Proxy;publicclassMainClass{publicstaticvoidmain(String[]args){SubjectrealnewRealSubject();MyHandlerhandlernewMyHandler();handler.setTarget(real);// 运行时生成代理实例必须基于接口getInterfaces()Subjectproxy(Subject)Proxy.newProxyInstance(real.getClass().getClassLoader(),real.getClass().getInterfaces(),handler);proxy.sailBook();}}注意invoke里是method.invoke(target, args)——target是被代理的真实对象不是proxy本身若写成method.invoke(proxy, args)会引发自身无限递归。七、动态代理的局限与实战延伸局限只能代理接口。JDK 动态代理要求目标实现接口若目标没有接口需用CGLIB通过继承目标类生成子类代理Spring AOP 在目标无接口时自动切换到 CGLIB。Struts2 拦截器其核心思路就是动态代理——在 Action 调用前后织入拦截逻辑。Spring AOP底层正是 JDK 动态代理有接口时 CGLIB无接口时双实现事务、Transactional、日志都是这么织入的。MyBatis Mapper你只写了UserMapper接口没写实现类运行时 MyBatis 用动态代理生成实现并返回本质也是同一套路。八、应用场景与优缺点典型应用场景远程代理RPC 的 stub客户端像调本地方法一样调远程服务。虚拟代理懒加载图片/大对象先用占位代理真正用到时才加载真实资源。保护代理代理做权限校验无权限直接拦截。缓存 / 日志 / 事务代理AOP 场景。优点职责清晰增强逻辑收口在代理业务类保持纯净。符合开闭原则新增增强无需改动目标对象。解耦客户端只依赖抽象接口。缺点静态代理类会随接口膨胀动态代理可读性略降、调试稍难。JDK 动态代理受必须实现接口限制无接口需 CGLIB。调用链多一层性能有极微小损耗一般可忽略。九、总结代理模式 给目标对象套一层同接口的代理由代理控制访问、在调用前后织入增强属于结构型模式。三角色抽象主题Subject、真实主题RealSubject、代理Proxy。静态代理简单直观但类易膨胀JDK 动态代理InvocationHandlerProxy.newProxyInstance在运行时自动生成代理适合增强逻辑通用、目标多的场景。选型口诀有接口 → JDK 动态代理无接口 → CGLIB想加横切逻辑又不想改业务 → 上代理。一句话总结想给对象加戏又不动它的代码套一层代理——前置打折、后置代金券业务类压根不知道。
返回列表