ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

GoF 代理模式详解

GoF 代理模式详解 GoF 代理模式详解一、什么是代理模式代理模式Proxy Pattern是 GoF 23 种设计模式中的一种结构型模式。它的核心思想是为其他对象提供一种代理以控制对这个对象的访问。通俗理解你不想或不能直接访问某个对象时通过一个“中间人”来访问。中间人可以在访问前后做一些额外处理比如权限校验、日志记录、延迟加载、事务控制等。生活中的例子租房找中介。房东是真实对象中介是代理。租客不直接找房东而是通过中介看房、签合同。中介可以在带看前审核租客资质签合同后收中介费。二、代理模式的结构代理模式包含三个角色角色说明抽象主题Subject定义真实对象和代理对象的公共接口真实主题RealSubject真正执行业务逻辑的对象代理Proxy持有真实对象的引用控制对真实对象的访问代理对象和真实对象实现同一个接口客户端面向接口编程不关心背后是真实对象还是代理。三、静态代理静态代理是最简单的实现方式手动编写代理类实现与目标类相同的接口在方法调用前后插入增强逻辑。代码示例抽象主题publicinterfaceUserService{voidsave(Useruser);UserfindById(Longid);}真实主题publicclassUserServiceImplimplementsUserService{Overridepublicvoidsave(Useruser){System.out.println(保存用户user.getName());}OverridepublicUserfindById(Longid){System.out.println(查询用户id);returnnewUser(id,张三);}}代理publicclassUserServiceProxyimplementsUserService{privatefinalUserServicetarget;publicUserServiceProxy(UserServicetarget){this.targettarget;}Overridepublicvoidsave(Useruser){System.out.println(【日志】开始保存用户);target.save(user);System.out.println(【日志】保存用户完成);}OverridepublicUserfindById(Longid){System.out.println(【日志】开始查询用户);Userusertarget.findById(id);System.out.println(【日志】查询用户完成);returnuser;}}客户端publicclassClient{publicstaticvoidmain(String[]args){UserServicetargetnewUserServiceImpl();UserServiceproxynewUserServiceProxy(target);proxy.save(newUser(1L,张三));}}静态代理的缺点类数量翻倍每个目标类都要写一个代理类。维护成本高接口新增方法时真实类和代理类都要修改。重复代码多个代理类的增强逻辑相似无法复用。静态代理只适合目标类少、增强逻辑简单的场景。四、动态代理动态代理在运行时动态生成代理类不需要手动编写。Java 中有两种主流实现JDK 动态代理和 CGLIB 动态代理。1. JDK 动态代理JDK 动态代理基于接口要求目标类必须实现至少一个接口。核心是java.lang.reflect.Proxy和InvocationHandler。publicclassJdkProxyHandlerimplementsInvocationHandler{privatefinalObjecttarget;publicJdkProxyHandler(Objecttarget){this.targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{System.out.println(【日志】调用方法method.getName());Objectresultmethod.invoke(target,args);System.out.println(【日志】方法执行完成);returnresult;}}生成代理对象UserServicetargetnewUserServiceImpl();UserServiceproxy(UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),newJdkProxyHandler(target));proxy.save(newUser(1L,张三));特点基于接口目标类必须实现接口。代理类和目标类实现同一个接口。通过InvocationHandler.invoke()拦截所有方法调用。生成代理类速度快但调用性能略低于直接调用。2. CGLIB 动态代理CGLIBCode Generation Library通过继承目标类生成子类来实现代理。目标类不需要实现接口但不能是 final 类被代理的方法不能是 final 或 private。publicclassCglibProxyInterceptorimplementsMethodInterceptor{OverridepublicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{System.out.println(【日志】调用方法method.getName());Objectresultproxy.invokeSuper(obj,args);System.out.println(【日志】方法执行完成);returnresult;}}生成代理对象EnhancerenhancernewEnhancer();enhancer.setSuperclass(UserServiceImpl.class);enhancer.setCallback(newCglibProxyInterceptor());UserServiceImplproxy(UserServiceImpl)enhancer.create();proxy.save(newUser(1L,张三));特点基于继承不需要接口。代理类是目标类的子类。无法代理 final 类和 final 方法。生成代理类较慢但调用性能优于 JDK 动态代理。3. JDK 动态代理 vs CGLIB对比维度JDK 动态代理CGLIB代理方式基于接口基于继承目标类要求必须实现接口不能是 final 类代理类实现同一接口继承目标类方法限制只能代理接口中定义的方法不能代理 final/private 方法性能生成快调用稍慢生成慢调用快依赖JDK 内置需要 CGLIB 库Spring 默认有接口时使用无接口时使用五、代理模式在 Spring 中的应用Spring AOP 的底层就是动态代理。当 Bean 需要被增强时如Transactional、Cacheable、Async、自定义切面Spring 会为 Bean 生成代理对象放入容器。1. 代理选择规则目标类实现了接口默认使用 JDK 动态代理。目标类没有实现接口使用 CGLIB。可以通过EnableAspectJAutoProxy(proxyTargetClass true)强制使用 CGLIB。Spring Boot 2.x 之后AOP 自动配置默认proxyTargetClass true即默认使用 CGLIB。2. 代理对开发的影响内部调用失效同一个类中方法 A 调用方法 B如果 B 需要被 AOP 增强不会生效。因为内部调用走的是原始对象不是代理对象。ServicepublicclassUserService{publicvoidmethodA(){methodB();// 直接调用不经过代理}TransactionalpublicvoidmethodB(){// 事务不会生效}}注入的是代理对象从容器中getBean()拿到的可能是代理对象不是原始对象。所以bean.getClass()可能不是原始类。3. Spring 中代理的典型场景Transactional事务代理在方法前后开启/提交/回滚事务。Cacheable缓存代理先查缓存未命中才执行方法。Async异步代理把方法调用提交到线程池。Spring Security安全代理方法调用前校验权限。自定义 AOP 切面日志、监控、限流等。六、代理模式 vs 装饰器模式两者都通过组合持有目标对象都实现同一接口容易混淆。对比维度代理模式装饰器模式目的控制对目标对象的访问动态给目标对象添加功能关注点访问控制、延迟加载、权限功能增强、组合扩展目标对象通常自己创建或注入由客户端传入代理数量通常一个代理对应一个目标可以多层装饰典型场景事务、权限、远程调用IO 流、Java 集合代理模式侧重“控制”装饰器模式侧重“增强”。但在实际应用中两者的边界有时模糊Spring AOP 既有控制事务也有增强日志。七、代理模式的优缺点优点降低耦合客户端面向接口不直接依赖真实对象。增强功能在访问前后插入日志、事务、权限等横切逻辑。控制访问可以限制对真实对象的访问实现延迟加载、权限校验。符合开闭原则新增代理不需要修改真实对象和客户端。缺点类数量增加静态代理需要为每个目标类写代理类。性能开销动态代理通过反射调用比直接调用慢。调试复杂代理链层层嵌套栈跟踪不直观。内部调用失效Spring AOP 中同一个类内部方法调用不经过代理。八、适用场景场景说明远程代理隐藏远程调用的网络细节如 RPC 框架虚拟代理延迟加载大对象如大图、大文件保护代理权限校验如 Spring Security缓存代理缓存查询结果如Cacheable日志代理记录方法调用日志事务代理声明式事务管理九、总结维度核心要点本质为其他对象提供代理控制对它的访问角色抽象主题、真实主题、代理静态代理手动编写代理类类数量多维护成本高JDK 动态代理基于接口ProxyInvocationHandlerCGLIB 动态代理基于继承EnhancerMethodInterceptorSpring AOP底层是动态代理有接口用 JDK无接口用 CGLIB关键问题内部调用失效、注入的是代理对象典型应用事务、缓存、异步、权限、日志代理模式是 Spring AOP 的基石。理解静态代理和两种动态代理的实现原理就能理解 Spring 如何在不修改业务代码的情况下为 Bean 添加事务、缓存、日志等横切能力。同时也能解释为什么Transactional在同类内部调用时会失效——因为内部调用没有经过代理对象。
返回列表