[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-13":26,"\u002Farticles\u002Fdesign-pattern-13\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-13\u002Fadjacent":71,"\u002Fcategories":90,"render-design-pattern-13":116},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113932970753527809","design-pattern-13","设计模式修炼之路 ⑬ 代理模式——控制访问，而非增强","控制访问而非增强、JDK 动态代理的 $Proxy0、CGLIB 与 Spring AOP 的老搭档","2113897628365230081",1,3804,9153,23,"2024-05-02T10:29:24","上一篇享元模式给它留足了排面：\"UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上\"。今天这位压轴的结构型选手登场：**代理模式（Proxy Pattern）**，GoF 23 人名单正牌选手、结构型七将的最后一位。装饰器回答\"怎么给它加功能\"，代理回答\"**怎么替它管住访问**\"——懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。前面十二篇的模式你也许日常真没写几个，但这一篇的东西，你的每一行 Spring 代码背后都在跑。\n\n## 一、什么是代理模式？\n\n先看定义：\n\n> 为其他对象提供一种代理以控制对这个对象的访问。\n\n翻译成人话：**在客户端和真实对象之间插一个\"替身\"——替身和真身实现同一个接口，客户端调的其实全是替身；替身可以在转发前做检查、在转发后做收尾，甚至压根不转发**。客户端对这一切无感，它以为自己一直在和真身说话。\n\n生活类比是明星经纪人：粉丝想找明星商演，联系的全是经纪人；经纪人审资质（权限校验）、排档期（延迟安排）、谈完再转告明星（转发真身），粉丝从头到尾以为自己在\"和明星那边\"打交道。对照一下装饰器的贴膜：贴膜是你自己跑到店里主动选的，经纪人是明星团队塞给你、你没法绕开的——**主动组装的叫装饰，被动接管的叫代理**，这句先记下，第五节展开。\n\n它有三个角色：\n\n- **Subject（抽象主题）**：真身和代理共同实现的接口，本篇里是 OrderService；\n- **RealSubject（真实主题）**：真正干活的对象，本篇里是 OrderServiceImpl；\n- **Proxy（代理）**：持有真身的引用，对外冒充真身，对内控制访问——本篇里先有手写的 OrderServiceProxy，后有运行时凭空生成的 $Proxy0。\n\n精髓一句话：**客户端只认 Subject，Proxy 里三层外三层地包着 RealSubject，包的那几层全是\"控制\"：这次让不让调？现在调还是待会儿调？在本地调还是发到远端调？**——问题千变万化，套路只有一个：替身。\n\n## 二、代理模式的优缺点\n\n### 优点\n\n1. **职责隔离，横切逻辑有处安放**：权限、日志、事务、监控这些\"每个方法都要、又都不属于业务\"的逻辑，从业务类里搬进代理——业务类一身轻松。\n2. **客户端零感知**：代理实现同一接口，客户端代码一行不改就获得了全部控制能力——Spring 里一个 @Transactional 注解，背后代理已经换了一茬，你的 Service 代码毫无察觉。\n3. **控制点丰富**：延迟创建（对象很重，用到再说）、拒绝访问（没权限直接 return）、远程转发（本地调用变网络调用）、结果缓存（第二次调用不出门）——全在转发前后那一小段里做文章。\n4. **动态代理通吃全家族**：静态代理一个接口写一个类，动态代理一份代码适配任意接口的任意方法——这就是 AOP 能\"一处声明、处处生效\"的根。\n\n### 缺点\n\n1. **类与间接层变多**：每个要代理的接口都得有代理（静态），或运行时凭空生成代理类（动态）——请求链路多了一跳，新手 debug 时满屏 $Proxy0、CGLIB$$，容易迷路。\n2. **JDK 动态代理有硬门槛**：目标类必须实现接口——Java 单继承，运行时生成的代理类已经把唯一的继承名额用在了 Proxy 类上，只剩\"实现接口\"这一条路。\n3. **CGLIB 有自己的坑**：靠生成子类工作，final 类、final\u002Fprivate\u002Fstatic 方法统统拦不住；代理对象和目标对象行为\"几乎\"一致，这个\"几乎\"偶尔咬人。\n4. **自调用失效**：目标对象内部 this.method() 的调用不走代理——@Transactional 方法里 this 调另一个事务方法，事务不生效，这是 Spring 面试必考题，病根就在代理这一层。\n\n## 三、代理模式的应用场景\n\n按\"控制什么\"分四类，每一类都有扛鼎框架：\n\n- **远程代理（Remote Proxy）——控制\"在哪调\"**：Dubbo、Feign 的客户端。你注入一个 XxxService 调方法，看似本地调用，代理把方法名和参数序列化成网络请求发到远端，再把返回值反序列化回来——RPC 的\"像调本地方法一样调远程\"全靠这层替身。\n- **虚拟代理（Virtual Proxy）——控制\"何时调\"**：懒加载。MyBatis 关联查询配置 lazy 后，你 getOrders() 拿到的不是真 List，是个代理 List——首次遍历时它才真正发 SQL（N+1 问题的元凶也是它，功过一体）。Hibernate 的懒加载实体同理。\n- **保护代理（Protection Proxy）——控制\"谁能量调\"**：Spring Security 的方法级鉴权，@PreAuthorize(\"hasRole('ADMIN')\") 后面站着的就是一圈代理在把门。\n- **缓存\u002F智能代理——控制\"要不要真调\"**：Spring 的 @Cacheable，命中缓存直接返回，方法体根本不执行；MyBatis 一级二级缓存也是包在查询外层的代理思想。\n\n再点名三个天天用却未必意识到的：\n\n- **Spring AOP 的全部**：@Transactional（事务开启\u002F提交\u002F回滚包在方法外）、@Async（调用线程换成线程池）、@Cacheable、全局日志切面——实现清一色动态代理。可以说没有代理模式就没有 Spring 的半条命。\n- **MyBatis 的 Mapper**：你只写接口、不写实现类，sqlSession.getMapper(OrderMapper.class) 拿到的是 MapperProxy 代理对象，把方法调用翻译成 SQL 执行——接口连实现类都不存在，全靠代理凭空造。本系列第六次在 MyBatis 撞见老朋友。\n- **Retrofit \u002F Feign 的 HTTP 客户端**：接口方法加注解，代理把方法调用变成 HTTP 请求——和 Mapper 一个思路。\n\n**什么时候该用**：想要的能力是\"包在方法外面的\"（校验、事务、日志、缓存、远程、延迟），且希望业务代码对此无感——就该在代理上做文章，而不是往业务方法里塞。\n\n## 四、代理模式的实现方式\n\n示例代码在 design-pattern 项目的 proxy 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── proxy                   \u002F\u002F 代理模式\n    ├── BadOrderService.java   \u002F\u002F 反面教材：横切逻辑长在业务里\n    ├── OrderService.java      \u002F\u002F 抽象主题：业务接口\n    ├── OrderServiceImpl.java  \u002F\u002F 真实主题：纯业务\n    ├── OrderServiceProxy.java \u002F\u002F 静态代理：手写的替身\n    ├── UserService.java       \u002F\u002F 第二个业务接口（证明动态代理不挑接口）\n    ├── UserServiceImpl.java   \u002F\u002F 第二个真实主题\n    ├── JdkProxyFactory.java   \u002F\u002F JDK 动态代理工厂\n    └── ProxyTest.java         \u002F\u002F 测试入口\n```\n\n场景：下单。权限校验、日志、耗时统计这些横切逻辑要有，但不能让它们长在业务里。三步走：先看病，再手写静态代理，最后上 JDK 动态代理。\n\n### 1. 反面教材：横切逻辑长在业务里\n\n```java\n\u002F\u002F 反面教材：权限校验、日志、耗时统计全横在业务方法里\npublic class BadOrderService {\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 横切逻辑 1：权限校验\n        if (!\"admin\".equals(user) && !\"vip\".equals(user)) {\n            System.out.println(\"[反面教材] \" + user + \" 无下单权限，拒绝\");\n            return;\n        }\n        \u002F\u002F 横切逻辑 2：日志\n        System.out.println(\"[反面教材] 开始下单：\" + orderId);\n        long start = System.currentTimeMillis();\n        \u002F\u002F —— 真正的业务，只有一行 ——\n        System.out.println(\"[反面教材] 订单 \" + orderId + \" 已创建\");\n        \u002F\u002F 横切逻辑 3：耗时统计\n        System.out.println(\"[反面教材] 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n```\n\n运行结果：\n\n```\n===== 反面教材：横切逻辑长在业务里 =====\n[反面教材] 开始下单：ORDER-1001\n[反面教材] 订单 ORDER-1001 已创建\n[反面教材] 耗时：6ms\n[反面教材] guest 无下单权限，拒绝\n```\n\n业务只有一行，旁边站着五个横切逻辑。三宗罪：\n\n1. **业务类被淹没**：想看\"下单到底干了啥\"，得在权限、日志、计时的夹缝里找那一行；\n2. **横切逻辑不可复用**：再有个 cancelOrder，同样的五段复制一遍；再有个 UserService，再复制一遍——N 个类 × M 个方法，复制 N×M 次；\n3. **改一处横切逻辑，全城大修**：日志格式从中文换英文？几十个方法挨个进。\n\n病根只有一个：**横切逻辑和业务逻辑写在同一个方法体里，物理上分不开**。\n\n### 2. 静态代理：手写一个替身\n\n先立接口，真身和代理的共同身份：\n\n```java\n\u002F\u002F 抽象主题：业务接口，真实对象和代理共同实现它，客户端只认它\npublic interface OrderService {\n    void placeOrder(String user, String orderId);\n}\n\n\u002F\u002F 真实主题：只写业务，一行横切逻辑都没有\npublic class OrderServiceImpl implements OrderService {\n    @Override\n    public void placeOrder(String user, String orderId) {\n        System.out.println(\"[真实对象] 订单 \" + orderId + \" 已创建\");\n    }\n}\n```\n\n替身登场：\n\n```java\n\u002F\u002F 静态代理：和真实主题实现同一个接口，在转发前后做访问控制\npublic class OrderServiceProxy implements OrderService {\n    private final OrderService target;\n\n    public OrderServiceProxy(OrderService target) {\n        this.target = target;\n    }\n\n    @Override\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 前置控制：权限校验，不合法直接拒之门外\n        if (!\"admin\".equals(user) && !\"vip\".equals(user)) {\n            System.out.println(\"[静态代理] \" + user + \" 无下单权限，拒绝\");\n            return;\n        }\n        System.out.println(\"[静态代理] 开始下单：\" + orderId);\n        long start = System.currentTimeMillis();\n        \u002F\u002F 转发给真实对象\n        target.placeOrder(user, orderId);\n        \u002F\u002F 后置控制：耗时统计\n        System.out.println(\"[静态代理] 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n```\n\n测试：\n\n```java\nOrderService proxy = new OrderServiceProxy(new OrderServiceImpl());\nproxy.placeOrder(\"vip\", \"ORDER-1001\");\nproxy.placeOrder(\"guest\", \"ORDER-1002\");\n```\n\n运行结果：\n\n```\n===== 正解 1：静态代理 =====\n[静态代理] 开始下单：ORDER-1001\n[真实对象] 订单 ORDER-1001 已创建\n[静态代理] 耗时：1ms\n[静态代理] guest 无下单权限，拒绝\n```\n\n立竿见影：OrderServiceImpl 回归一行业务的纯粹，权限、日志、计时全搬进了代理；客户端只认 OrderService 接口，它分不清（也不需要分清）手里是真是假。guest 那单被拦在门外，真身根本不知道世界上有过这单——**控制访问**，四个字落地。\n\n但静态代理的死穴也在这儿：**一个接口一个代理类**。OrderService 写个 OrderServiceProxy，UserService 就得写个 UserServiceProxy，接口一多，代理类成灾；而且 placeOrder 加个 cancelOrder 方法，代理类也得跟着加一份转发——纯搬运，毫无营养。\n\n### 3. JDK 动态代理：运行时凭空造替身\n\nJDK 里藏着一个神器：Proxy.newProxyInstance——运行时动态生成代理类，把\"实现接口 + 转发\"这套体力活全包了，你只需要提供一个 InvocationHandler，告诉它\"方法调用进来时怎么处理\"：\n\n```java\n\u002F\u002F JDK 动态代理工厂：运行时生成代理类，一个 InvocationHandler 适配任意接口的任意方法\npublic class JdkProxyFactory {\n\n    @SuppressWarnings(\"unchecked\")\n    public static \u003CT> T create(T target) {\n        return (T) Proxy.newProxyInstance(\n                target.getClass().getClassLoader(),\n                target.getClass().getInterfaces(),\n                new InvocationHandler() {\n                    @Override\n                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n                        \u002F\u002F 任意接口的任意方法调用，都先路由到这——AOP 的雏形\n                        System.out.println(\"[JDK代理] 开始调用 \" + method.getName() + \"，参数：\" + Arrays.toString(args));\n                        long start = System.currentTimeMillis();\n                        Object result = method.invoke(target, args);\n                        System.out.println(\"[JDK代理] \" + method.getName() + \" 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n                        return result;\n                    }\n                });\n    }\n}\n```\n\n一个工厂，通吃两个互不相识的接口：\n\n```java\nOrderService orderProxy = JdkProxyFactory.create(new OrderServiceImpl());\norderProxy.placeOrder(\"admin\", \"ORDER-1001\");\n\nUserService userProxy = JdkProxyFactory.create(new UserServiceImpl());\nString name = userProxy.getUserName(\"U-9527\");\nSystem.out.println(\"拿到结果：\" + name);\n\n\u002F\u002F 看看 JDK 在运行时生成的代理类到底叫什么\nSystem.out.println(\"orderProxy 的真实类名：\" + orderProxy.getClass().getName());\n```\n\n运行结果：\n\n```\n===== 正解 2：JDK 动态代理 =====\n[JDK代理] 开始调用 placeOrder，参数：[admin, ORDER-1001]\n[真实对象] 订单 ORDER-1001 已创建\n[JDK代理] placeOrder 耗时：0ms\n[JDK代理] 开始调用 getUserName，参数：[U-9527]\n[真实对象] 查询用户：U-9527\n[JDK代理] getUserName 耗时：0ms\n拿到结果：用户U-9527\norderProxy 的真实类名：jdk.proxy1.$Proxy0\n```\n\n最后一行是本篇的彩蛋：orderProxy 的类名是 **jdk.proxy1.$Proxy0**——你翻遍整个项目源码都找不到这个类，它是 Proxy.newProxyInstance 在运行时动态生成、编译、加载进 JVM 的。想象一下它大致长什么样：\n\n```java\n\u002F\u002F JDK 运行时生成的代理类（示意，还原大致结构）\npublic final class $Proxy0 extends Proxy implements OrderService {\n    public $Proxy0(InvocationHandler h) { super(h); }\n\n    @Override\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 没有一行自己的逻辑：把\"哪个方法、什么参数\"打包，全部路由给 InvocationHandler\n        super.h.invoke(this, placeOrderMethod, new Object[]{user, orderId});\n    }\n}\n```\n\n看懂这个结构，三件事同时明白：\n\n1. **为什么必须基于接口**：$Proxy0 的 extends 名额已经给了 Proxy 类，Java 单继承，它只剩\"实现接口\"一条路能和 OrderService 挂上钩——目标类没接口，JDK 动态代理直接罢工；\n2. **为什么一份 Handler 通吃所有方法**：每个方法的方法体都是同一句\"打包路由给 h\"，method 对象在运行时才知道是 placeOrder 还是 getUserName——invoke 里那个 method.getName() 打印什么，全看客户端调了什么；\n3. **AOP 的本质**：把上面这个 invoke 换成\"先开事务 → method.invoke → 提交事务 → catch 里回滚\"，就是 Spring @Transactional 的全部原理。所谓切面编程，就是给 InvocationHandler 加了\"哪些方法、什么条件才拦\"的配置化能力。\n\n## 五、CGLIB：没有接口，还有子类\n\nJDK 动态代理的门槛是\"必须有接口\"，可老项目里一堆没接口的类怎么办？CGLIB（Code Generation Library）换了个思路：**不实现接口了，直接生成目标类的子类**，重写非 final 方法来做拦截：\n\n```java\n\u002F\u002F CGLIB 的用法（示意，需要引入 cglib 或 spring 的 repackaged 版本）\nEnhancer enhancer = new Enhancer();\nenhancer.setSuperclass(OrderServiceImpl.class);          \u002F\u002F 目标类：当爹\nenhancer.setCallback(new MethodInterceptor() {\n    @Override\n    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {\n        System.out.println(\"[CGLIB] 拦截到 \" + method.getName());   \u002F\u002F 相当于 invoke\n        return proxy.invokeSuper(obj, args);                        \u002F\u002F 相当于转发，但走的是 FastClass 机制，比纯反射快\n    }\n});\nOrderServiceImpl proxy = (OrderServiceImpl) enhancer.create();  \u002F\u002F 产物是 OrderServiceImpl 的子类\n```\n\n两套动态代理摆在一起看：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>JDK 动态代理\u003C\u002Fth>\u003Cth>CGLIB\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>依赖\u003C\u002Ftd>\u003Ctd>JDK 自带，零依赖\u003C\u002Ftd>\u003Ctd>第三方库（Spring 已内置打包版）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>前提\u003C\u002Ftd>\u003Ctd>目标类实现接口\u003C\u002Ftd>\u003Ctd>目标类可被继承\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>手段\u003C\u002Ftd>\u003Ctd>运行时生成\"实现同一接口\"的类\u003C\u002Ftd>\u003Ctd>运行时生成\"目标类的子类\"\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>拦不住的\u003C\u002Ftd>\u003Ctd>接口里没有的方法\u003C\u002Ftd>\u003Ctd>final 类、final\u002Fprivate\u002Fstatic 方法\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>拦截回调\u003C\u002Ftd>\u003Ctd>InvocationHandler.invoke\u003C\u002Ftd>\u003Ctd>MethodInterceptor.intercept\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Spring 的选择\u003C\u002Ftd>\u003Ctd>目标有接口时（旧默认）\u003C\u002Ftd>\u003Ctd>Spring Boot 2.x 起全面默认\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\nSpring Boot 2.x 为什么把默认改成 CGLIB（proxyTargetClass=true）？因为 JDK 代理只认接口，容器里注入具体类型就报错——`@Autowired OrderServiceImpl` 在 JDK 代理下直接翻车，代理对象根本不是 OrderServiceImpl 的实例。CGLIB 代理是子类，is-a 关系还在，怎么注都不炸——框架选择\"默认不咬人\"的方案，天经地义。\n\n## 六、代理 vs 装饰器：一模一样的结构，相反的意图\n\n五节之前欠的债现在还：代理和装饰器，UML 放一起你根本分不出谁是谁——都实现 Subject、都持有 Subject 字段、方法体都是\"干点自己的 + 转发\"。GoF 把它们列成两个模式，分的不是结构，是**意图**：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>装饰器模式\u003C\u002Fth>\u003Cth>代理模式\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>意图\u003C\u002Ftd>\u003Ctd>**增强**：给对象加职责\u003C\u002Ftd>\u003Ctd>**控制**：管住对对象的访问\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>谁组装\u003C\u002Ftd>\u003Ctd>客户端主动一层层 new\u003C\u002Ftd>\u003Ctd>框架\u002F工厂悄悄塞给你\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>客户端知情吗\u003C\u002Ftd>\u003Ctd>知道，就是它自己套的\u003C\u002Ftd>\u003Ctd>不知道，以为在调真身\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>生命周期\u003C\u002Ftd>\u003Ctd>装饰器被动接收被装饰对象\u003C\u002Ftd>\u003Ctd>代理常常自己掌管真身（懒加载时真身还没出生）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>层数\u003C\u002Ftd>\u003Ctd>随便叠，三层五层皆可\u003C\u002Ftd>\u003Ctd>通常就一层\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n三连问做个了断：\n\n1. `new BufferedInputStream(new FileInputStream(file))`——谁写的这行？你，客户端自己。你清楚知道自己在层层加料，这是**装饰器**；\n2. `getMapper(OrderMapper.class)`——谁给你的这个对象？MyBatis。你不知道也不关心它是不是替身，它甚至没有真身。这是**代理**；\n3. Spring 容器里 `@Autowired` 进来的 Service——框架给的，带事务带日志的全功能替身，**代理**。\n\n再补一个工程上的判别法：**看构造器参数的语义**。装饰器的构造参数是\"被装饰者\"，纯转发目的；代理的构造参数常常是\"配置\"或干脆无参——因为真身要么由代理延迟创建，要么在远端根本不存在。\n\n## 写在最后\n\n回顾这一篇：代理模式在客户端和真实对象之间安插替身——同接口、持真身、控访问；静态代理手写一个类，胜在直观、败在一接口一类；JDK 动态代理运行时生成 $Proxy0，一份 InvocationHandler 通吃任意接口任意方法，代价是目标必须有接口；CGLIB 生成子类补上这块短板，拦不住的换成了 final\u002Fprivate\u002Fstatic；远程、懒加载、保护、缓存四类代理各霸一方，Spring AOP、MyBatis Mapper、Feign、Retrofit 全是它的产业；和装饰器的分界不在结构在意图——主动加料是装饰，无感接管是代理。至此，结构型七将（本系列含过滤器共八员）全部收官。\n\n下一篇 ⑭ 模板模式，行为型第一位出场，整个系列进入下半场。结构型管\"类怎么组装\"，行为型管\"类怎么协作\"——模板模式打头阵，干的事你天天在干：父类把流程骨架焊死，子类只往空位里填肉。HttpServlet 的 doGet\u002FdoPost、AbstractList 的 get、JUnit 的生命周期注解，还有那个\"面试必问\"的 AQS，全是它的地盘。\n","\u003Cp>上一篇享元模式给它留足了排面：\"UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上\"。今天这位压轴的结构型选手登场：\u003Cstrong>代理模式（Proxy Pattern）\u003C\u002Fstrong>，GoF 23 人名单正牌选手、结构型七将的最后一位。装饰器回答\"怎么给它加功能\"，代理回答\"\u003Cstrong>怎么替它管住访问\u003C\u002Fstrong>\"——懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。前面十二篇的模式你也许日常真没写几个，但这一篇的东西，你的每一行 Spring 代码背后都在跑。\u003C\u002Fp>\n\u003Ch2>一、什么是代理模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>为其他对象提供一种代理以控制对这个对象的访问。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>在客户端和真实对象之间插一个\"替身\"——替身和真身实现同一个接口，客户端调的其实全是替身；替身可以在转发前做检查、在转发后做收尾，甚至压根不转发\u003C\u002Fstrong>。客户端对这一切无感，它以为自己一直在和真身说话。\u003C\u002Fp>\n\u003Cp>生活类比是明星经纪人：粉丝想找明星商演，联系的全是经纪人；经纪人审资质（权限校验）、排档期（延迟安排）、谈完再转告明星（转发真身），粉丝从头到尾以为自己在\"和明星那边\"打交道。对照一下装饰器的贴膜：贴膜是你自己跑到店里主动选的，经纪人是明星团队塞给你、你没法绕开的——\u003Cstrong>主动组装的叫装饰，被动接管的叫代理\u003C\u002Fstrong>，这句先记下，第五节展开。\u003C\u002Fp>\n\u003Cp>它有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Subject（抽象主题）\u003C\u002Fstrong>：真身和代理共同实现的接口，本篇里是 OrderService；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>RealSubject（真实主题）\u003C\u002Fstrong>：真正干活的对象，本篇里是 OrderServiceImpl；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Proxy（代理）\u003C\u002Fstrong>：持有真身的引用，对外冒充真身，对内控制访问——本篇里先有手写的 OrderServiceProxy，后有运行时凭空生成的 $Proxy0。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>精髓一句话：\u003Cstrong>客户端只认 Subject，Proxy 里三层外三层地包着 RealSubject，包的那几层全是\"控制\"：这次让不让调？现在调还是待会儿调？在本地调还是发到远端调？\u003C\u002Fstrong>——问题千变万化，套路只有一个：替身。\u003C\u002Fp>\n\u003Ch2>二、代理模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>职责隔离，横切逻辑有处安放\u003C\u002Fstrong>：权限、日志、事务、监控这些\"每个方法都要、又都不属于业务\"的逻辑，从业务类里搬进代理——业务类一身轻松。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>客户端零感知\u003C\u002Fstrong>：代理实现同一接口，客户端代码一行不改就获得了全部控制能力——Spring 里一个 @Transactional 注解，背后代理已经换了一茬，你的 Service 代码毫无察觉。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>控制点丰富\u003C\u002Fstrong>：延迟创建（对象很重，用到再说）、拒绝访问（没权限直接 return）、远程转发（本地调用变网络调用）、结果缓存（第二次调用不出门）——全在转发前后那一小段里做文章。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态代理通吃全家族\u003C\u002Fstrong>：静态代理一个接口写一个类，动态代理一份代码适配任意接口的任意方法——这就是 AOP 能\"一处声明、处处生效\"的根。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>类与间接层变多\u003C\u002Fstrong>：每个要代理的接口都得有代理（静态），或运行时凭空生成代理类（动态）——请求链路多了一跳，新手 debug 时满屏 $Proxy0、CGLIB$$，容易迷路。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>JDK 动态代理有硬门槛\u003C\u002Fstrong>：目标类必须实现接口——Java 单继承，运行时生成的代理类已经把唯一的继承名额用在了 Proxy 类上，只剩\"实现接口\"这一条路。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>CGLIB 有自己的坑\u003C\u002Fstrong>：靠生成子类工作，final 类、final\u002Fprivate\u002Fstatic 方法统统拦不住；代理对象和目标对象行为\"几乎\"一致，这个\"几乎\"偶尔咬人。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>自调用失效\u003C\u002Fstrong>：目标对象内部 this.method() 的调用不走代理——@Transactional 方法里 this 调另一个事务方法，事务不生效，这是 Spring 面试必考题，病根就在代理这一层。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、代理模式的应用场景\u003C\u002Fh2>\n\u003Cp>按\"控制什么\"分四类，每一类都有扛鼎框架：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>远程代理（Remote Proxy）——控制\"在哪调\"\u003C\u002Fstrong>：Dubbo、Feign 的客户端。你注入一个 XxxService 调方法，看似本地调用，代理把方法名和参数序列化成网络请求发到远端，再把返回值反序列化回来——RPC 的\"像调本地方法一样调远程\"全靠这层替身。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>虚拟代理（Virtual Proxy）——控制\"何时调\"\u003C\u002Fstrong>：懒加载。MyBatis 关联查询配置 lazy 后，你 getOrders() 拿到的不是真 List，是个代理 List——首次遍历时它才真正发 SQL（N+1 问题的元凶也是它，功过一体）。Hibernate 的懒加载实体同理。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>保护代理（Protection Proxy）——控制\"谁能量调\"\u003C\u002Fstrong>：Spring Security 的方法级鉴权，@PreAuthorize(\"hasRole('ADMIN')\") 后面站着的就是一圈代理在把门。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>缓存\u002F智能代理——控制\"要不要真调\"\u003C\u002Fstrong>：Spring 的 @Cacheable，命中缓存直接返回，方法体根本不执行；MyBatis 一级二级缓存也是包在查询外层的代理思想。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>再点名三个天天用却未必意识到的：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Spring AOP 的全部\u003C\u002Fstrong>：@Transactional（事务开启\u002F提交\u002F回滚包在方法外）、@Async（调用线程换成线程池）、@Cacheable、全局日志切面——实现清一色动态代理。可以说没有代理模式就没有 Spring 的半条命。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MyBatis 的 Mapper\u003C\u002Fstrong>：你只写接口、不写实现类，sqlSession.getMapper(OrderMapper.class) 拿到的是 MapperProxy 代理对象，把方法调用翻译成 SQL 执行——接口连实现类都不存在，全靠代理凭空造。本系列第六次在 MyBatis 撞见老朋友。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Retrofit \u002F Feign 的 HTTP 客户端\u003C\u002Fstrong>：接口方法加注解，代理把方法调用变成 HTTP 请求——和 Mapper 一个思路。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：想要的能力是\"包在方法外面的\"（校验、事务、日志、缓存、远程、延迟），且希望业务代码对此无感——就该在代理上做文章，而不是往业务方法里塞。\u003C\u002Fp>\n\u003Ch2>四、代理模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 proxy 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── proxy                   \u002F\u002F 代理模式\n    ├── BadOrderService.java   \u002F\u002F 反面教材：横切逻辑长在业务里\n    ├── OrderService.java      \u002F\u002F 抽象主题：业务接口\n    ├── OrderServiceImpl.java  \u002F\u002F 真实主题：纯业务\n    ├── OrderServiceProxy.java \u002F\u002F 静态代理：手写的替身\n    ├── UserService.java       \u002F\u002F 第二个业务接口（证明动态代理不挑接口）\n    ├── UserServiceImpl.java   \u002F\u002F 第二个真实主题\n    ├── JdkProxyFactory.java   \u002F\u002F JDK 动态代理工厂\n    └── ProxyTest.java         \u002F\u002F 测试入口\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：下单。权限校验、日志、耗时统计这些横切逻辑要有，但不能让它们长在业务里。三步走：先看病，再手写静态代理，最后上 JDK 动态代理。\u003C\u002Fp>\n\u003Ch3>1. 反面教材：横切逻辑长在业务里\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 反面教材：权限校验、日志、耗时统计全横在业务方法里\npublic class BadOrderService {\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 横切逻辑 1：权限校验\n        if (!\"admin\".equals(user) &amp;&amp; !\"vip\".equals(user)) {\n            System.out.println(\"[反面教材] \" + user + \" 无下单权限，拒绝\");\n            return;\n        }\n        \u002F\u002F 横切逻辑 2：日志\n        System.out.println(\"[反面教材] 开始下单：\" + orderId);\n        long start = System.currentTimeMillis();\n        \u002F\u002F —— 真正的业务，只有一行 ——\n        System.out.println(\"[反面教材] 订单 \" + orderId + \" 已创建\");\n        \u002F\u002F 横切逻辑 3：耗时统计\n        System.out.println(\"[反面教材] 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 反面教材：横切逻辑长在业务里 =====\n[反面教材] 开始下单：ORDER-1001\n[反面教材] 订单 ORDER-1001 已创建\n[反面教材] 耗时：6ms\n[反面教材] guest 无下单权限，拒绝\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>业务只有一行，旁边站着五个横切逻辑。三宗罪：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>业务类被淹没\u003C\u002Fstrong>：想看\"下单到底干了啥\"，得在权限、日志、计时的夹缝里找那一行；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>横切逻辑不可复用\u003C\u002Fstrong>：再有个 cancelOrder，同样的五段复制一遍；再有个 UserService，再复制一遍——N 个类 × M 个方法，复制 N×M 次；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>改一处横切逻辑，全城大修\u003C\u002Fstrong>：日志格式从中文换英文？几十个方法挨个进。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>病根只有一个：\u003Cstrong>横切逻辑和业务逻辑写在同一个方法体里，物理上分不开\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch3>2. 静态代理：手写一个替身\u003C\u002Fh3>\n\u003Cp>先立接口，真身和代理的共同身份：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 抽象主题：业务接口，真实对象和代理共同实现它，客户端只认它\npublic interface OrderService {\n    void placeOrder(String user, String orderId);\n}\n\n\u002F\u002F 真实主题：只写业务，一行横切逻辑都没有\npublic class OrderServiceImpl implements OrderService {\n    @Override\n    public void placeOrder(String user, String orderId) {\n        System.out.println(\"[真实对象] 订单 \" + orderId + \" 已创建\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>替身登场：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 静态代理：和真实主题实现同一个接口，在转发前后做访问控制\npublic class OrderServiceProxy implements OrderService {\n    private final OrderService target;\n\n    public OrderServiceProxy(OrderService target) {\n        this.target = target;\n    }\n\n    @Override\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 前置控制：权限校验，不合法直接拒之门外\n        if (!\"admin\".equals(user) &amp;&amp; !\"vip\".equals(user)) {\n            System.out.println(\"[静态代理] \" + user + \" 无下单权限，拒绝\");\n            return;\n        }\n        System.out.println(\"[静态代理] 开始下单：\" + orderId);\n        long start = System.currentTimeMillis();\n        \u002F\u002F 转发给真实对象\n        target.placeOrder(user, orderId);\n        \u002F\u002F 后置控制：耗时统计\n        System.out.println(\"[静态代理] 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">OrderService proxy = new OrderServiceProxy(new OrderServiceImpl());\nproxy.placeOrder(\"vip\", \"ORDER-1001\");\nproxy.placeOrder(\"guest\", \"ORDER-1002\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 正解 1：静态代理 =====\n[静态代理] 开始下单：ORDER-1001\n[真实对象] 订单 ORDER-1001 已创建\n[静态代理] 耗时：1ms\n[静态代理] guest 无下单权限，拒绝\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>立竿见影：OrderServiceImpl 回归一行业务的纯粹，权限、日志、计时全搬进了代理；客户端只认 OrderService 接口，它分不清（也不需要分清）手里是真是假。guest 那单被拦在门外，真身根本不知道世界上有过这单——\u003Cstrong>控制访问\u003C\u002Fstrong>，四个字落地。\u003C\u002Fp>\n\u003Cp>但静态代理的死穴也在这儿：\u003Cstrong>一个接口一个代理类\u003C\u002Fstrong>。OrderService 写个 OrderServiceProxy，UserService 就得写个 UserServiceProxy，接口一多，代理类成灾；而且 placeOrder 加个 cancelOrder 方法，代理类也得跟着加一份转发——纯搬运，毫无营养。\u003C\u002Fp>\n\u003Ch3>3. JDK 动态代理：运行时凭空造替身\u003C\u002Fh3>\n\u003Cp>JDK 里藏着一个神器：Proxy.newProxyInstance——运行时动态生成代理类，把\"实现接口 + 转发\"这套体力活全包了，你只需要提供一个 InvocationHandler，告诉它\"方法调用进来时怎么处理\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F JDK 动态代理工厂：运行时生成代理类，一个 InvocationHandler 适配任意接口的任意方法\npublic class JdkProxyFactory {\n\n    @SuppressWarnings(\"unchecked\")\n    public static &lt;T&gt; T create(T target) {\n        return (T) Proxy.newProxyInstance(\n                target.getClass().getClassLoader(),\n                target.getClass().getInterfaces(),\n                new InvocationHandler() {\n                    @Override\n                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n                        \u002F\u002F 任意接口的任意方法调用，都先路由到这——AOP 的雏形\n                        System.out.println(\"[JDK代理] 开始调用 \" + method.getName() + \"，参数：\" + Arrays.toString(args));\n                        long start = System.currentTimeMillis();\n                        Object result = method.invoke(target, args);\n                        System.out.println(\"[JDK代理] \" + method.getName() + \" 耗时：\" + (System.currentTimeMillis() - start) + \"ms\");\n                        return result;\n                    }\n                });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>一个工厂，通吃两个互不相识的接口：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">OrderService orderProxy = JdkProxyFactory.create(new OrderServiceImpl());\norderProxy.placeOrder(\"admin\", \"ORDER-1001\");\n\nUserService userProxy = JdkProxyFactory.create(new UserServiceImpl());\nString name = userProxy.getUserName(\"U-9527\");\nSystem.out.println(\"拿到结果：\" + name);\n\n\u002F\u002F 看看 JDK 在运行时生成的代理类到底叫什么\nSystem.out.println(\"orderProxy 的真实类名：\" + orderProxy.getClass().getName());\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 正解 2：JDK 动态代理 =====\n[JDK代理] 开始调用 placeOrder，参数：[admin, ORDER-1001]\n[真实对象] 订单 ORDER-1001 已创建\n[JDK代理] placeOrder 耗时：0ms\n[JDK代理] 开始调用 getUserName，参数：[U-9527]\n[真实对象] 查询用户：U-9527\n[JDK代理] getUserName 耗时：0ms\n拿到结果：用户U-9527\norderProxy 的真实类名：jdk.proxy1.$Proxy0\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>最后一行是本篇的彩蛋：orderProxy 的类名是 \u003Cstrong>jdk.proxy1.$Proxy0\u003C\u002Fstrong>——你翻遍整个项目源码都找不到这个类，它是 Proxy.newProxyInstance 在运行时动态生成、编译、加载进 JVM 的。想象一下它大致长什么样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F JDK 运行时生成的代理类（示意，还原大致结构）\npublic final class $Proxy0 extends Proxy implements OrderService {\n    public $Proxy0(InvocationHandler h) { super(h); }\n\n    @Override\n    public void placeOrder(String user, String orderId) {\n        \u002F\u002F 没有一行自己的逻辑：把\"哪个方法、什么参数\"打包，全部路由给 InvocationHandler\n        super.h.invoke(this, placeOrderMethod, new Object[]{user, orderId});\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>看懂这个结构，三件事同时明白：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>为什么必须基于接口\u003C\u002Fstrong>：$Proxy0 的 extends 名额已经给了 Proxy 类，Java 单继承，它只剩\"实现接口\"一条路能和 OrderService 挂上钩——目标类没接口，JDK 动态代理直接罢工；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>为什么一份 Handler 通吃所有方法\u003C\u002Fstrong>：每个方法的方法体都是同一句\"打包路由给 h\"，method 对象在运行时才知道是 placeOrder 还是 getUserName——invoke 里那个 method.getName() 打印什么，全看客户端调了什么；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>AOP 的本质\u003C\u002Fstrong>：把上面这个 invoke 换成\"先开事务 → method.invoke → 提交事务 → catch 里回滚\"，就是 Spring @Transactional 的全部原理。所谓切面编程，就是给 InvocationHandler 加了\"哪些方法、什么条件才拦\"的配置化能力。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>五、CGLIB：没有接口，还有子类\u003C\u002Fh2>\n\u003Cp>JDK 动态代理的门槛是\"必须有接口\"，可老项目里一堆没接口的类怎么办？CGLIB（Code Generation Library）换了个思路：\u003Cstrong>不实现接口了，直接生成目标类的子类\u003C\u002Fstrong>，重写非 final 方法来做拦截：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F CGLIB 的用法（示意，需要引入 cglib 或 spring 的 repackaged 版本）\nEnhancer enhancer = new Enhancer();\nenhancer.setSuperclass(OrderServiceImpl.class);          \u002F\u002F 目标类：当爹\nenhancer.setCallback(new MethodInterceptor() {\n    @Override\n    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {\n        System.out.println(\"[CGLIB] 拦截到 \" + method.getName());   \u002F\u002F 相当于 invoke\n        return proxy.invokeSuper(obj, args);                        \u002F\u002F 相当于转发，但走的是 FastClass 机制，比纯反射快\n    }\n});\nOrderServiceImpl proxy = (OrderServiceImpl) enhancer.create();  \u002F\u002F 产物是 OrderServiceImpl 的子类\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>两套动态代理摆在一起看：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>JDK 动态代理\u003C\u002Fth>\n   \u003Cth>CGLIB\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>依赖\u003C\u002Ftd>\n   \u003Ctd>JDK 自带，零依赖\u003C\u002Ftd>\n   \u003Ctd>第三方库（Spring 已内置打包版）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>前提\u003C\u002Ftd>\n   \u003Ctd>目标类实现接口\u003C\u002Ftd>\n   \u003Ctd>目标类可被继承\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>手段\u003C\u002Ftd>\n   \u003Ctd>运行时生成\"实现同一接口\"的类\u003C\u002Ftd>\n   \u003Ctd>运行时生成\"目标类的子类\"\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拦不住的\u003C\u002Ftd>\n   \u003Ctd>接口里没有的方法\u003C\u002Ftd>\n   \u003Ctd>final 类、final\u002Fprivate\u002Fstatic 方法\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拦截回调\u003C\u002Ftd>\n   \u003Ctd>InvocationHandler.invoke\u003C\u002Ftd>\n   \u003Ctd>MethodInterceptor.intercept\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Spring 的选择\u003C\u002Ftd>\n   \u003Ctd>目标有接口时（旧默认）\u003C\u002Ftd>\n   \u003Ctd>Spring Boot 2.x 起全面默认\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Spring Boot 2.x 为什么把默认改成 CGLIB（proxyTargetClass=true）？因为 JDK 代理只认接口，容器里注入具体类型就报错——\u003Ccode>@Autowired OrderServiceImpl\u003C\u002Fcode> 在 JDK 代理下直接翻车，代理对象根本不是 OrderServiceImpl 的实例。CGLIB 代理是子类，is-a 关系还在，怎么注都不炸——框架选择\"默认不咬人\"的方案，天经地义。\u003C\u002Fp>\n\u003Ch2>六、代理 vs 装饰器：一模一样的结构，相反的意图\u003C\u002Fh2>\n\u003Cp>五节之前欠的债现在还：代理和装饰器，UML 放一起你根本分不出谁是谁——都实现 Subject、都持有 Subject 字段、方法体都是\"干点自己的 + 转发\"。GoF 把它们列成两个模式，分的不是结构，是\u003Cstrong>意图\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>装饰器模式\u003C\u002Fth>\n   \u003Cth>代理模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>意图\u003C\u002Ftd>\n   \u003Ctd>**增强**：给对象加职责\u003C\u002Ftd>\n   \u003Ctd>**控制**：管住对对象的访问\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>谁组装\u003C\u002Ftd>\n   \u003Ctd>客户端主动一层层 new\u003C\u002Ftd>\n   \u003Ctd>框架\u002F工厂悄悄塞给你\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>客户端知情吗\u003C\u002Ftd>\n   \u003Ctd>知道，就是它自己套的\u003C\u002Ftd>\n   \u003Ctd>不知道，以为在调真身\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>生命周期\u003C\u002Ftd>\n   \u003Ctd>装饰器被动接收被装饰对象\u003C\u002Ftd>\n   \u003Ctd>代理常常自己掌管真身（懒加载时真身还没出生）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>层数\u003C\u002Ftd>\n   \u003Ctd>随便叠，三层五层皆可\u003C\u002Ftd>\n   \u003Ctd>通常就一层\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>三连问做个了断：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Ccode>new BufferedInputStream(new FileInputStream(file))\u003C\u002Fcode>——谁写的这行？你，客户端自己。你清楚知道自己在层层加料，这是\u003Cstrong>装饰器\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>\u003Ccode>getMapper(OrderMapper.class)\u003C\u002Fcode>——谁给你的这个对象？MyBatis。你不知道也不关心它是不是替身，它甚至没有真身。这是\u003Cstrong>代理\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>Spring 容器里 \u003Ccode>@Autowired\u003C\u002Fcode> 进来的 Service——框架给的，带事务带日志的全功能替身，\u003Cstrong>代理\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>再补一个工程上的判别法：\u003Cstrong>看构造器参数的语义\u003C\u002Fstrong>。装饰器的构造参数是\"被装饰者\"，纯转发目的；代理的构造参数常常是\"配置\"或干脆无参——因为真身要么由代理延迟创建，要么在远端根本不存在。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：代理模式在客户端和真实对象之间安插替身——同接口、持真身、控访问；静态代理手写一个类，胜在直观、败在一接口一类；JDK 动态代理运行时生成 $Proxy0，一份 InvocationHandler 通吃任意接口任意方法，代价是目标必须有接口；CGLIB 生成子类补上这块短板，拦不住的换成了 final\u002Fprivate\u002Fstatic；远程、懒加载、保护、缓存四类代理各霸一方，Spring AOP、MyBatis Mapper、Feign、Retrofit 全是它的产业；和装饰器的分界不在结构在意图——主动加料是装饰，无感接管是代理。至此，结构型七将（本系列含过滤器共八员）全部收官。\u003C\u002Fp>\n\u003Cp>下一篇 ⑭ 模板模式，行为型第一位出场，整个系列进入下半场。结构型管\"类怎么组装\"，行为型管\"类怎么协作\"——模板模式打头阵，干的事你天天在干：父类把流程骨架焊死，子类只往空位里填肉。HttpServlet 的 doGet\u002FdoPost、AbstractList 的 get、JUnit 的生命周期注解，还有那个\"面试必问\"的 AQS，全是它的地盘。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,62],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113933638763548673","design-pattern-11","设计模式修炼之路 ⑪ 外观模式——给杂乱子系统一个入口","给杂乱子系统一个统一入口、只编排不干活、SLF4J 名字里的 Facade",3176,7681,20,"2024-03-20T15:49:38",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":52,"publishedAt":61},"2113931625669595138","design-pattern-10","设计模式修炼之路 ⑩ 装饰器模式——不改原类，动态加功能","不改原类动态加功能、\"我是组件+我有组件\"、Java IO 流的套娃设计",1003,7847,"2024-02-10T15:14:16",{"id":63,"slug":64,"title":65,"summary":66,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":67,"likeCount":4,"wordCount":68,"readingMinutes":69,"publishedAt":70},"2113934922031828994","design-pattern-27","设计模式修炼之路 ㉗ 业务代表模式——替表示层挡住远程的脏活","替表示层挡住远程脏活、查找+重试+异常翻译一肩挑、重试风暴、外观\u002F代理\u002F业务代表三兄弟对账、Feign 转世",1811,6147,16,"2025-03-25T21:44:13",{"code":4,"message":5,"data":72},{"prev":73,"next":82},{"id":74,"slug":75,"title":76,"summary":77,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":78,"likeCount":4,"wordCount":79,"readingMinutes":80,"publishedAt":81},"2113931783249596417","design-pattern-12","设计模式修炼之路 ⑫ 享元模式——共享细粒度对象","共享细粒度对象、内部状态与外部状态的切分、Integer 缓存与字符串常量池",2866,6956,18,"2024-04-15T17:46:59",{"id":83,"slug":84,"title":85,"summary":86,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":87,"likeCount":4,"wordCount":88,"readingMinutes":36,"publishedAt":89},"2113933745433088002","design-pattern-14","设计模式修炼之路 ⑭ 模板模式——焊死骨架，留白细节","final 焊死流程骨架、四种方法各司其职、钩子是选答题、从 Servlet 到 AQS 全是填空",3418,8834,"2024-06-13T15:33:43",{"code":4,"message":5,"data":91},[92,98,103,109],{"id":93,"name":94,"slug":95,"description":96,"sortOrder":4,"articleCount":97},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":99,"slug":100,"description":101,"sortOrder":4,"articleCount":102},"设计模式","design-pattern","设计模式系列","34",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":33,"articleCount":108},"1946000000000000002","随笔","essay","生活与思考","0",{"id":110,"name":111,"slug":112,"description":113,"sortOrder":114,"articleCount":115},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":117,"toc":118},"\u003Cp>上一篇享元模式给它留足了排面：\"UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上\"。今天这位压轴的结构型选手登场：\u003Cstrong>代理模式（Proxy Pattern）\u003C\u002Fstrong>，GoF 23 人名单正牌选手、结构型七将的最后一位。装饰器回答\"怎么给它加功能\"，代理回答\"\u003Cstrong>怎么替它管住访问\u003C\u002Fstrong>\"——懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。前面十二篇的模式你也许日常真没写几个，但这一篇的东西，你的每一行 Spring 代码背后都在跑。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是代理模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>为其他对象提供一种代理以控制对这个对象的访问。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>在客户端和真实对象之间插一个\"替身\"——替身和真身实现同一个接口，客户端调的其实全是替身；替身可以在转发前做检查、在转发后做收尾，甚至压根不转发\u003C\u002Fstrong>。客户端对这一切无感，它以为自己一直在和真身说话。\u003C\u002Fp>\n\u003Cp>生活类比是明星经纪人：粉丝想找明星商演，联系的全是经纪人；经纪人审资质（权限校验）、排档期（延迟安排）、谈完再转告明星（转发真身），粉丝从头到尾以为自己在\"和明星那边\"打交道。对照一下装饰器的贴膜：贴膜是你自己跑到店里主动选的，经纪人是明星团队塞给你、你没法绕开的——\u003Cstrong>主动组装的叫装饰，被动接管的叫代理\u003C\u002Fstrong>，这句先记下，第五节展开。\u003C\u002Fp>\n\u003Cp>它有三个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Subject（抽象主题）\u003C\u002Fstrong>：真身和代理共同实现的接口，本篇里是 OrderService；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>RealSubject（真实主题）\u003C\u002Fstrong>：真正干活的对象，本篇里是 OrderServiceImpl；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Proxy（代理）\u003C\u002Fstrong>：持有真身的引用，对外冒充真身，对内控制访问——本篇里先有手写的 OrderServiceProxy，后有运行时凭空生成的 $Proxy0。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>精髓一句话：\u003Cstrong>客户端只认 Subject，Proxy 里三层外三层地包着 RealSubject，包的那几层全是\"控制\"：这次让不让调？现在调还是待会儿调？在本地调还是发到远端调？\u003C\u002Fstrong>——问题千变万化，套路只有一个：替身。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、代理模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>职责隔离，横切逻辑有处安放\u003C\u002Fstrong>：权限、日志、事务、监控这些\"每个方法都要、又都不属于业务\"的逻辑，从业务类里搬进代理——业务类一身轻松。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>客户端零感知\u003C\u002Fstrong>：代理实现同一接口，客户端代码一行不改就获得了全部控制能力——Spring 里一个 @Transactional 注解，背后代理已经换了一茬，你的 Service 代码毫无察觉。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>控制点丰富\u003C\u002Fstrong>：延迟创建（对象很重，用到再说）、拒绝访问（没权限直接 return）、远程转发（本地调用变网络调用）、结果缓存（第二次调用不出门）——全在转发前后那一小段里做文章。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态代理通吃全家族\u003C\u002Fstrong>：静态代理一个接口写一个类，动态代理一份代码适配任意接口的任意方法——这就是 AOP 能\"一处声明、处处生效\"的根。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>类与间接层变多\u003C\u002Fstrong>：每个要代理的接口都得有代理（静态），或运行时凭空生成代理类（动态）——请求链路多了一跳，新手 debug 时满屏 $Proxy0、CGLIB$$，容易迷路。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>JDK 动态代理有硬门槛\u003C\u002Fstrong>：目标类必须实现接口——Java 单继承，运行时生成的代理类已经把唯一的继承名额用在了 Proxy 类上，只剩\"实现接口\"这一条路。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>CGLIB 有自己的坑\u003C\u002Fstrong>：靠生成子类工作，final 类、final\u002Fprivate\u002Fstatic 方法统统拦不住；代理对象和目标对象行为\"几乎\"一致，这个\"几乎\"偶尔咬人。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>自调用失效\u003C\u002Fstrong>：目标对象内部 this.method() 的调用不走代理——@Transactional 方法里 this 调另一个事务方法，事务不生效，这是 Spring 面试必考题，病根就在代理这一层。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、代理模式的应用场景\u003C\u002Fh2>\n\u003Cp>按\"控制什么\"分四类，每一类都有扛鼎框架：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>远程代理（Remote Proxy）——控制\"在哪调\"\u003C\u002Fstrong>：Dubbo、Feign 的客户端。你注入一个 XxxService 调方法，看似本地调用，代理把方法名和参数序列化成网络请求发到远端，再把返回值反序列化回来——RPC 的\"像调本地方法一样调远程\"全靠这层替身。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>虚拟代理（Virtual Proxy）——控制\"何时调\"\u003C\u002Fstrong>：懒加载。MyBatis 关联查询配置 lazy 后，你 getOrders() 拿到的不是真 List，是个代理 List——首次遍历时它才真正发 SQL（N+1 问题的元凶也是它，功过一体）。Hibernate 的懒加载实体同理。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>保护代理（Protection Proxy）——控制\"谁能量调\"\u003C\u002Fstrong>：Spring Security 的方法级鉴权，@PreAuthorize(\"hasRole('ADMIN')\") 后面站着的就是一圈代理在把门。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>缓存\u002F智能代理——控制\"要不要真调\"\u003C\u002Fstrong>：Spring 的 @Cacheable，命中缓存直接返回，方法体根本不执行；MyBatis 一级二级缓存也是包在查询外层的代理思想。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>再点名三个天天用却未必意识到的：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Spring AOP 的全部\u003C\u002Fstrong>：@Transactional（事务开启\u002F提交\u002F回滚包在方法外）、@Async（调用线程换成线程池）、@Cacheable、全局日志切面——实现清一色动态代理。可以说没有代理模式就没有 Spring 的半条命。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>MyBatis 的 Mapper\u003C\u002Fstrong>：你只写接口、不写实现类，sqlSession.getMapper(OrderMapper.class) 拿到的是 MapperProxy 代理对象，把方法调用翻译成 SQL 执行——接口连实现类都不存在，全靠代理凭空造。本系列第六次在 MyBatis 撞见老朋友。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Retrofit \u002F Feign 的 HTTP 客户端\u003C\u002Fstrong>：接口方法加注解，代理把方法调用变成 HTTP 请求——和 Mapper 一个思路。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：想要的能力是\"包在方法外面的\"（校验、事务、日志、缓存、远程、延迟），且希望业务代码对此无感——就该在代理上做文章，而不是往业务方法里塞。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、代理模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 proxy 包里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>design-pattern\u002Fsrc\u002Fmain\u002Fjava\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>└── proxy                   \u002F\u002F 代理模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── BadOrderService.java   \u002F\u002F 反面教材：横切逻辑长在业务里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── OrderService.java      \u002F\u002F 抽象主题：业务接口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── OrderServiceImpl.java  \u002F\u002F 真实主题：纯业务\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── OrderServiceProxy.java \u002F\u002F 静态代理：手写的替身\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── UserService.java       \u002F\u002F 第二个业务接口（证明动态代理不挑接口）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── UserServiceImpl.java   \u002F\u002F 第二个真实主题\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── JdkProxyFactory.java   \u002F\u002F JDK 动态代理工厂\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── ProxyTest.java         \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：下单。权限校验、日志、耗时统计这些横切逻辑要有，但不能让它们长在业务里。三步走：先看病，再手写静态代理，最后上 JDK 动态代理。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">1. 反面教材：横切逻辑长在业务里\u003C\u002Fh3>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 反面教材：权限校验、日志、耗时统计全横在业务方法里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 横切逻辑 1：权限校验\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"admin\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> !\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"vip\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[反面教材] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> user \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 无下单权限，拒绝\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 横切逻辑 2：日志\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[反面教材] 开始下单：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        long\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F —— 真正的业务，只有一行 ——\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[反面教材] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 已创建\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 横切逻辑 3：耗时统计\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[反面教材] 耗时：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"ms\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 反面教材：横切逻辑长在业务里 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[反面教材] 开始下单：ORDER-1001\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[反面教材] 订单 ORDER-1001 已创建\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[反面教材] 耗时：6ms\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[反面教材] guest 无下单权限，拒绝\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>业务只有一行，旁边站着五个横切逻辑。三宗罪：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>业务类被淹没\u003C\u002Fstrong>：想看\"下单到底干了啥\"，得在权限、日志、计时的夹缝里找那一行；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>横切逻辑不可复用\u003C\u002Fstrong>：再有个 cancelOrder，同样的五段复制一遍；再有个 UserService，再复制一遍——N 个类 × M 个方法，复制 N×M 次；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>改一处横切逻辑，全城大修\u003C\u002Fstrong>：日志格式从中文换英文？几十个方法挨个进。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>病根只有一个：\u003Cstrong>横切逻辑和业务逻辑写在同一个方法体里，物理上分不开\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 静态代理：手写一个替身\u003C\u002Fh3>\n\u003Cp>先立接口，真身和代理的共同身份：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 抽象主题：业务接口，真实对象和代理共同实现它，客户端只认它\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 真实主题：只写业务，一行横切逻辑都没有\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceImpl\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[真实对象] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 已创建\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>替身登场：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 静态代理：和真实主题实现同一个接口，在转发前后做访问控制\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceProxy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> OrderService target;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceProxy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderService \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">target\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.target \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> target;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 前置控制：权限校验，不合法直接拒之门外\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">!\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"admin\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> !\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"vip\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">equals\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[静态代理] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> user \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 无下单权限，拒绝\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[静态代理] 开始下单：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        long\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 转发给真实对象\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        target.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(user, orderId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 后置控制：耗时统计\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[静态代理] 耗时：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"ms\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">OrderService proxy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceProxy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceImpl\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">proxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"vip\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"ORDER-1001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">proxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"guest\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"ORDER-1002\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 正解 1：静态代理 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[静态代理] 开始下单：ORDER-1001\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[真实对象] 订单 ORDER-1001 已创建\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[静态代理] 耗时：1ms\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[静态代理] guest 无下单权限，拒绝\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>立竿见影：OrderServiceImpl 回归一行业务的纯粹，权限、日志、计时全搬进了代理；客户端只认 OrderService 接口，它分不清（也不需要分清）手里是真是假。guest 那单被拦在门外，真身根本不知道世界上有过这单——\u003Cstrong>控制访问\u003C\u002Fstrong>，四个字落地。\u003C\u002Fp>\n\u003Cp>但静态代理的死穴也在这儿：\u003Cstrong>一个接口一个代理类\u003C\u002Fstrong>。OrderService 写个 OrderServiceProxy，UserService 就得写个 UserServiceProxy，接口一多，代理类成灾；而且 placeOrder 加个 cancelOrder 方法，代理类也得跟着加一份转发——纯搬运，毫无营养。\u003C\u002Fp>\n\u003Ch3 id=\"toc-9\">3. JDK 动态代理：运行时凭空造替身\u003C\u002Fh3>\n\u003Cp>JDK 里藏着一个神器：Proxy.newProxyInstance——运行时动态生成代理类，把\"实现接口 + 转发\"这套体力活全包了，你只需要提供一个 InvocationHandler，告诉它\"方法调用进来时怎么处理\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F JDK 动态代理工厂：运行时生成代理类，一个 InvocationHandler 适配任意接口的任意方法\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> JdkProxyFactory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">SuppressWarnings\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"unchecked\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> &#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">T\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> T \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">create\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(T \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">target\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (T) Proxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">newProxyInstance\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                target.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getClass\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getClassLoader\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                target.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getClass\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getInterfaces\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(),\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> InvocationHandler\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Object \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">invoke\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Object \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">proxy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Method \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">method\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Object\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">throws\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Throwable {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">                        \u002F\u002F 任意接口的任意方法调用，都先路由到这——AOP 的雏形\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[JDK代理] 开始调用 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> method.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"，参数：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Arrays.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(args));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                        long\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                        Object result \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> method.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">invoke\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(target, args);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[JDK代理] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> method.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 耗时：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (System.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">currentTimeMillis\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> start) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"ms\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">                        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> result;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">                });\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>一个工厂，通吃两个互不相识的接口：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">OrderService orderProxy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> JdkProxyFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">create\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderServiceImpl\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">orderProxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"admin\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"ORDER-1001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">UserService userProxy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> JdkProxyFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">create\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> UserServiceImpl\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> userProxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getUserName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"U-9527\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"拿到结果：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 看看 JDK 在运行时生成的代理类到底叫什么\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"orderProxy 的真实类名：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderProxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getClass\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 正解 2：JDK 动态代理 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[JDK代理] 开始调用 placeOrder，参数：[admin, ORDER-1001]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[真实对象] 订单 ORDER-1001 已创建\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[JDK代理] placeOrder 耗时：0ms\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[JDK代理] 开始调用 getUserName，参数：[U-9527]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[真实对象] 查询用户：U-9527\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[JDK代理] getUserName 耗时：0ms\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>拿到结果：用户U-9527\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>orderProxy 的真实类名：jdk.proxy1.$Proxy0\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>最后一行是本篇的彩蛋：orderProxy 的类名是 \u003Cstrong>jdk.proxy1.$Proxy0\u003C\u002Fstrong>——你翻遍整个项目源码都找不到这个类，它是 Proxy.newProxyInstance 在运行时动态生成、编译、加载进 JVM 的。想象一下它大致长什么样：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F JDK 运行时生成的代理类（示意，还原大致结构）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> $Proxy0\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Proxy\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> $\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">Proxy0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(InvocationHandler \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">h\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) { \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(h); }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> placeOrder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">user\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 没有一行自己的逻辑：把\"哪个方法、什么参数\"打包，全部路由给 InvocationHandler\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.h.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">invoke\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, placeOrderMethod, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> Object\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[]{user, orderId});\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>看懂这个结构，三件事同时明白：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>为什么必须基于接口\u003C\u002Fstrong>：$Proxy0 的 extends 名额已经给了 Proxy 类，Java 单继承，它只剩\"实现接口\"一条路能和 OrderService 挂上钩——目标类没接口，JDK 动态代理直接罢工；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>为什么一份 Handler 通吃所有方法\u003C\u002Fstrong>：每个方法的方法体都是同一句\"打包路由给 h\"，method 对象在运行时才知道是 placeOrder 还是 getUserName——invoke 里那个 method.getName() 打印什么，全看客户端调了什么；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>AOP 的本质\u003C\u002Fstrong>：把上面这个 invoke 换成\"先开事务 → method.invoke → 提交事务 → catch 里回滚\"，就是 Spring @Transactional 的全部原理。所谓切面编程，就是给 InvocationHandler 加了\"哪些方法、什么条件才拦\"的配置化能力。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-10\">五、CGLIB：没有接口，还有子类\u003C\u002Fh2>\n\u003Cp>JDK 动态代理的门槛是\"必须有接口\"，可老项目里一堆没接口的类怎么办？CGLIB（Code Generation Library）换了个思路：\u003Cstrong>不实现接口了，直接生成目标类的子类\u003C\u002Fstrong>，重写非 final 方法来做拦截：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F CGLIB 的用法（示意，需要引入 cglib 或 spring 的 repackaged 版本）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Enhancer enhancer \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Enhancer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">enhancer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setSuperclass\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderServiceImpl.class);          \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 目标类：当爹\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">enhancer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setCallback\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MethodInterceptor\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Object \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">intercept\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Object \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">obj\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, Method \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">method\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Object\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, MethodProxy \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">proxy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">throws\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Throwable {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[CGLIB] 拦截到 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> method.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 相当于 invoke\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> proxy.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">invokeSuper\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(obj, args);                        \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 相当于转发，但走的是 FastClass 机制，比纯反射快\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">});\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">OrderServiceImpl proxy \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (OrderServiceImpl) enhancer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">create\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 产物是 OrderServiceImpl 的子类\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>两套动态代理摆在一起看：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>JDK 动态代理\u003C\u002Fth>\n   \u003Cth>CGLIB\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>依赖\u003C\u002Ftd>\n   \u003Ctd>JDK 自带，零依赖\u003C\u002Ftd>\n   \u003Ctd>第三方库（Spring 已内置打包版）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>前提\u003C\u002Ftd>\n   \u003Ctd>目标类实现接口\u003C\u002Ftd>\n   \u003Ctd>目标类可被继承\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>手段\u003C\u002Ftd>\n   \u003Ctd>运行时生成\"实现同一接口\"的类\u003C\u002Ftd>\n   \u003Ctd>运行时生成\"目标类的子类\"\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拦不住的\u003C\u002Ftd>\n   \u003Ctd>接口里没有的方法\u003C\u002Ftd>\n   \u003Ctd>final 类、final\u002Fprivate\u002Fstatic 方法\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拦截回调\u003C\u002Ftd>\n   \u003Ctd>InvocationHandler.invoke\u003C\u002Ftd>\n   \u003Ctd>MethodInterceptor.intercept\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Spring 的选择\u003C\u002Ftd>\n   \u003Ctd>目标有接口时（旧默认）\u003C\u002Ftd>\n   \u003Ctd>Spring Boot 2.x 起全面默认\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Spring Boot 2.x 为什么把默认改成 CGLIB（proxyTargetClass=true）？因为 JDK 代理只认接口，容器里注入具体类型就报错——\u003Ccode>@Autowired OrderServiceImpl\u003C\u002Fcode> 在 JDK 代理下直接翻车，代理对象根本不是 OrderServiceImpl 的实例。CGLIB 代理是子类，is-a 关系还在，怎么注都不炸——框架选择\"默认不咬人\"的方案，天经地义。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">六、代理 vs 装饰器：一模一样的结构，相反的意图\u003C\u002Fh2>\n\u003Cp>五节之前欠的债现在还：代理和装饰器，UML 放一起你根本分不出谁是谁——都实现 Subject、都持有 Subject 字段、方法体都是\"干点自己的 + 转发\"。GoF 把它们列成两个模式，分的不是结构，是\u003Cstrong>意图\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>装饰器模式\u003C\u002Fth>\n   \u003Cth>代理模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>意图\u003C\u002Ftd>\n   \u003Ctd>**增强**：给对象加职责\u003C\u002Ftd>\n   \u003Ctd>**控制**：管住对对象的访问\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>谁组装\u003C\u002Ftd>\n   \u003Ctd>客户端主动一层层 new\u003C\u002Ftd>\n   \u003Ctd>框架\u002F工厂悄悄塞给你\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>客户端知情吗\u003C\u002Ftd>\n   \u003Ctd>知道，就是它自己套的\u003C\u002Ftd>\n   \u003Ctd>不知道，以为在调真身\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>生命周期\u003C\u002Ftd>\n   \u003Ctd>装饰器被动接收被装饰对象\u003C\u002Ftd>\n   \u003Ctd>代理常常自己掌管真身（懒加载时真身还没出生）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>层数\u003C\u002Ftd>\n   \u003Ctd>随便叠，三层五层皆可\u003C\u002Ftd>\n   \u003Ctd>通常就一层\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>三连问做个了断：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Ccode>new BufferedInputStream(new FileInputStream(file))\u003C\u002Fcode>——谁写的这行？你，客户端自己。你清楚知道自己在层层加料，这是\u003Cstrong>装饰器\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>\u003Ccode>getMapper(OrderMapper.class)\u003C\u002Fcode>——谁给你的这个对象？MyBatis。你不知道也不关心它是不是替身，它甚至没有真身。这是\u003Cstrong>代理\u003C\u002Fstrong>；\u003C\u002Fli>\n \u003Cli>Spring 容器里 \u003Ccode>@Autowired\u003C\u002Fcode> 进来的 Service——框架给的，带事务带日志的全功能替身，\u003Cstrong>代理\u003C\u002Fstrong>。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>再补一个工程上的判别法：\u003Cstrong>看构造器参数的语义\u003C\u002Fstrong>。装饰器的构造参数是\"被装饰者\"，纯转发目的；代理的构造参数常常是\"配置\"或干脆无参——因为真身要么由代理延迟创建，要么在远端根本不存在。\u003C\u002Fp>\n\u003Ch2 id=\"toc-12\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：代理模式在客户端和真实对象之间安插替身——同接口、持真身、控访问；静态代理手写一个类，胜在直观、败在一接口一类；JDK 动态代理运行时生成 $Proxy0，一份 InvocationHandler 通吃任意接口任意方法，代价是目标必须有接口；CGLIB 生成子类补上这块短板，拦不住的换成了 final\u002Fprivate\u002Fstatic；远程、懒加载、保护、缓存四类代理各霸一方，Spring AOP、MyBatis Mapper、Feign、Retrofit 全是它的产业；和装饰器的分界不在结构在意图——主动加料是装饰，无感接管是代理。至此，结构型七将（本系列含过滤器共八员）全部收官。\u003C\u002Fp>\n\u003Cp>下一篇 ⑭ 模板模式，行为型第一位出场，整个系列进入下半场。结构型管\"类怎么组装\"，行为型管\"类怎么协作\"——模板模式打头阵，干的事你天天在干：父类把流程骨架焊死，子类只往空位里填肉。HttpServlet 的 doGet\u002FdoPost、AbstractList 的 get、JUnit 的生命周期注解，还有那个\"面试必问\"的 AQS，全是它的地盘。\u003C\u002Fp>",[119,122,125,129,132,135,138,141,144,147,150,153],{"id":120,"text":121,"level":114},"toc-1","一、什么是代理模式？",{"id":123,"text":124,"level":114},"toc-2","二、代理模式的优缺点",{"id":126,"text":127,"level":128},"toc-3","优点",3,{"id":130,"text":131,"level":128},"toc-4","缺点",{"id":133,"text":134,"level":114},"toc-5","三、代理模式的应用场景",{"id":136,"text":137,"level":114},"toc-6","四、代理模式的实现方式",{"id":139,"text":140,"level":128},"toc-7","1. 反面教材：横切逻辑长在业务里",{"id":142,"text":143,"level":128},"toc-8","2. 静态代理：手写一个替身",{"id":145,"text":146,"level":128},"toc-9","3. JDK 动态代理：运行时凭空造替身",{"id":148,"text":149,"level":114},"toc-10","五、CGLIB：没有接口，还有子类",{"id":151,"text":152,"level":114},"toc-11","六、代理 vs 装饰器：一模一样的结构，相反的意图",{"id":154,"text":155,"level":114},"toc-12","写在最后"]