Java动态代理深度解析:JDK与CGLIB选型及Spring AOP实战
1. 为什么动态代理值得单独拎出来聊刚入行那会儿我第一次在项目里看到Proxy.newProxyInstance这行代码整个人是懵的。明明没有实现类为什么方法就能跑起来后来做支付对接、做日志埋点、做权限校验几乎每个环节都能看到动态代理的影子我才意识到这东西不是面试八股而是日常开发里真正会反复用到的底层能力。这篇文章不打算照本宣科地讲 API而是想把我这些年踩过的坑、做过的取舍、以及实际项目里怎么选型完整地摊开来讲。JAVA 动态代理本质上解决的是一个非常朴素的问题在不修改原有类的前提下给方法调用前后插入额外逻辑。听起来简单但它的实现方式、性能表现、适用边界直接决定了你在架构设计时能不能用得顺手。适合谁看如果你写过 Spring 的 AOP、用过 MyBatis 的 Mapper 接口、或者被面试官问过 JDK 代理和 CGLIB 代理的区别那这篇内容会对你有用。如果你只是听说过这个词也没关系我会从最基础的概念开始用生活化的类比把原理讲透再一步步带你写出可运行的代码。需要提前说明的是动态代理本身不复杂复杂的是它背后的字节码生成机制和类加载时机。很多人卡住不是因为不会写而是不清楚“为什么这样写能生效”。所以我会在关键节点解释设计意图而不是只丢一段代码让你抄。2. 动态代理到底在解决什么问题2.1 从静态代理的痛点说起要理解动态代理得先知道静态代理为什么不够用。假设你有一个用户服务接口里面定义了查询、新增、删除三个方法。现在产品要求每个方法执行前打印日志执行后统计耗时。最直接的做法是写一个代理类持有真实对象的引用在每个方法里手动加上日志和计时逻辑。这就是静态代理。问题在于如果接口有二十个方法你就要写二十遍重复的日志代码如果再来一个订单服务也要加同样的逻辑你又得再写一个代理类。代码量爆炸维护成本极高。我早期做过一个项目光是日志代理类就写了十几个后来需求一变改一处逻辑要动十几个文件那滋味真的不好受。静态代理的核心矛盾在于代理逻辑和业务逻辑耦合在编译期就固定死了无法复用也无法动态扩展。2.2 动态代理的核心思路动态代理的思路是代理类不在编译期写死而是在运行期由程序自动生成。你只需要告诉它三件事——代理谁、代理什么逻辑、怎么创建。剩下的字节码拼装、类加载、实例化全部交给框架处理。打个比方静态代理像是你提前写好了一份合同模板每次有新业务就手动改一遍动态代理则是你准备了一个万能模板业务方只要填几个参数系统自动生成对应的合同。前者靠人力后者靠机制。在 JAVA 里动态代理主要有两条技术路线一条是 JDK 原生提供的基于接口的代理另一条是第三方库 CGLIB 提供的基于继承的代理。两者各有适用场景选错了会直接报错后面我会详细对比。2.3 动态代理的典型应用场景动态代理不是炫技用的它在实际项目里承担着非常具体的职责。我整理了几类最常见的场景日志记录与链路追踪在方法调用前后自动埋点记录入参、出参、耗时不需要业务代码里写一行日志。事务管理Spring 的Transactional就是通过动态代理实现的方法执行前开启事务异常时回滚正常时提交。权限校验在方法执行前检查当前用户是否有权限没有则直接拦截避免在每个业务方法里重复写校验逻辑。远程调用像 MyBatis 的 Mapper 接口你只定义了接口没有实现类框架通过动态代理生成实现把方法调用转换成 SQL 执行。缓存处理方法调用前先查缓存命中则直接返回未命中才走真实逻辑并把结果写回缓存。这些场景的共同点是横切逻辑与业务逻辑分离代理层负责通用能力业务层只关心自己的事。这也是 AOP面向切面编程的核心思想而动态代理正是 AOP 的底层支撑。3. JDK 动态代理的实现细节与实操3.1 核心 API 拆解JDK 动态代理涉及两个关键角色InvocationHandler和Proxy。InvocationHandler是一个接口只有一个方法invoke。所有对代理对象的方法调用最终都会转发到这个invoke方法里。你可以把它理解成一个“总机”不管外面拨哪个分机号都先接到总机由总机决定怎么转接。Proxy类提供了一个静态方法newProxyInstance用来生成代理对象。它接收三个参数参数类型作用classLoaderClassLoader定义代理类的类加载器interfacesClass[]代理类要实现的接口列表handlerInvocationHandler方法调用的实际处理器这三个参数里最容易出问题的是第二个。JDK 动态代理只能代理接口如果你传的是一个没有实现任何接口的类会直接抛出异常。这是它和 CGLIB 最本质的区别。3.2 手写一个可运行的 JDK 代理示例光说理论没意思直接上代码。假设我们有一个订单服务接口定义如下public interface OrderService { String createOrder(String userId, String productId); boolean cancelOrder(String orderId); }真实实现类public class OrderServiceImpl implements OrderService { Override public String createOrder(String userId, String productId) { System.out.println(创建订单: 用户 userId , 商品 productId); return ORDER_ System.currentTimeMillis(); } Override public boolean cancelOrder(String orderId) { System.out.println(取消订单: orderId); return true; } }现在写一个处理器在方法调用前后加日志和计时public class LogInvocationHandler implements InvocationHandler { private final Object target; public LogInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start System.currentTimeMillis(); System.out.println([日志] 开始执行: method.getName()); Object result method.invoke(target, args); long cost System.currentTimeMillis() - start; System.out.println([日志] 执行结束: method.getName() , 耗时 cost ms); return result; } }生成代理对象并调用public class Main { public static void main(String[] args) { OrderService realService new OrderServiceImpl(); OrderService proxyService (OrderService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), new LogInvocationHandler(realService) ); proxyService.createOrder(U001, P100); proxyService.cancelOrder(ORDER_123); } }跑起来之后你会看到日志和计时逻辑自动包裹在了真实方法外面而OrderServiceImpl里一行多余代码都没有。这就是动态代理的威力。3.3 为什么 JDK 代理必须基于接口很多人会问为什么 JDK 代理不能直接代理一个类答案藏在它的实现机制里。JDK 动态代理生成的代理类会继承Proxy类并实现你指定的所有接口。由于 JAVA 是单继承的代理类已经继承了Proxy就没法再继承你的目标类了。所以它只能通过实现接口的方式来保证代理对象和目标对象具有相同的方法签名。这个限制在实际开发中影响不大因为面向接口编程本来就是主流实践。但如果你要代理的是一个没有接口的类那就只能换 CGLIB 了。注意JDK 代理生成的代理类其equals、hashCode、toString方法也会被转发到InvocationHandler如果不做特殊处理可能会出现意料之外的行为。我一般会在invoke里判断一下方法所属的声明类如果是Object类的方法就直接调用目标对象。3.4 实操心得代理对象的类型转换陷阱有一次我在项目里写了一个通用代理工具方法返回类型是Object调用方直接强转成目标接口。结果在某些场景下抛了ClassCastException排查了半天才发现是因为类加载器不一致导致的。具体来说如果目标对象是由某个自定义类加载器加载的而你在生成代理时传了另一个类加载器生成的代理类就无法被正确转换。解决办法是始终使用目标对象的类加载器或者使用线程上下文类加载器。这个坑不常遇到但一旦遇到就很难查。4. CGLIB 代理与 JDK 代理的选型对比4.1 CGLIB 的工作原理CGLIB 的思路和 JDK 完全不同。它不要求目标类实现接口而是通过继承目标类来生成代理。具体做法是在运行期动态生成一个目标类的子类并重写父类的所有非 final 方法在重写的方法里插入拦截逻辑。因为是通过继承实现的所以有两个硬性限制第一目标类不能是 final 的第二目标方法不能是 final 的。否则无法重写代理就失效了。CGLIB 底层依赖 ASM 字节码框架直接操作 class 文件性能上比 JDK 代理在创建阶段稍慢但在调用阶段通常更快。不过随着 JDK 版本迭代JDK 代理的性能已经大幅提升两者的差距在日常业务中几乎可以忽略。4.2 两者的核心差异对照对比维度JDK 动态代理CGLIB 代理代理方式实现接口继承目标类目标类要求必须实现接口不能是 final 类方法要求接口中定义的方法不能是 final/private 方法创建速度较快较慢调用速度较慢反射较快字节码依赖JDK 原生需要引入第三方库适用场景面向接口编程无接口的类代理4.3 实际项目里怎么选我的经验是优先用 JDK 代理。原因有三点。第一面向接口编程本身就是好习惯如果你的类没有接口可能说明设计上还有优化空间。第二JDK 代理是原生支持的不引入额外依赖减少版本冲突风险。第三Spring 框架默认也是优先使用 JDK 代理只有在目标类没有接口时才退化为 CGLIB。当然也有例外。比如你要代理的是一个第三方库里的类它没有实现接口你又不能改它的源码那就只能用 CGLIB。或者你对性能有极致要求且目标方法调用非常频繁CGLIB 的调用速度优势可能会体现出来。提示Spring Boot 从 2.x 开始默认将spring.aop.proxy-target-class设置为true也就是默认使用 CGLIB 代理。这个变化的原因是为了避免在某些场景下因接口代理导致的类型转换问题。如果你有特殊需求可以在配置文件里改回 JDK 代理。4.4 一个容易忽略的坑代理对象的注入问题在使用 Spring 的时候如果你在一个 Bean 内部直接调用另一个被代理的方法代理逻辑是不会生效的。因为内部调用走的是this引用而不是代理对象。这个问题在事务场景下特别常见你在同一个类里方法 A 调用方法 B方法 B 上标了Transactional结果事务不生效。解决办法是把方法 B 抽到另一个 Bean 里或者通过AopContext.currentProxy()获取当前代理对象再调用。我当初在这个坑上浪费了整整一个下午后来养成了一个习惯只要涉及代理增强的方法绝对不在同一个类里内部调用。5. 动态代理在主流框架中的落地方式5.1 Spring AOP 的代理机制Spring AOP 是动态代理最广为人知的应用。它的核心流程是容器启动时扫描所有标注了切面注解的 Bean判断是否需要代理。如果需要就根据目标类是否实现接口选择 JDK 代理或 CGLIB 代理生成代理对象并注册到容器中。当你从容器里获取 Bean 时拿到的其实是代理对象。调用方法时会先经过切面链再到达真实方法。整个过程对业务代码完全透明。Spring 里有两个关键接口MethodInterceptor和Advisor。前者定义拦截逻辑后者把切点和通知组合在一起。理解这两个接口就能看懂 Spring AOP 的大部分源码。5.2 MyBatis Mapper 接口的代理实现MyBatis 的 Mapper 接口没有实现类但你可以直接注入使用。这背后就是 JDK 动态代理在起作用。MyBatis 在启动时会为每个 Mapper 接口生成一个代理对象。当你调用接口方法时代理会拦截调用根据方法名和注解或 XML 配置找到对应的 SQL 语句执行数据库操作再把结果映射成 JAVA 对象返回。这里的关键类是MapperProxy它实现了InvocationHandler。在invoke方法里它会根据方法签名查找MapperMethod然后执行对应的 SQL 命令。整个过程把接口调用转换成了数据库操作非常巧妙。5.3 自定义代理在业务中的实践除了框架层面的应用我也在业务里手写过动态代理。有一次需要做一个通用的接口幂等校验要求对所有标注了特定注解的方法自动检查请求是否重复。做法是写一个IdempotentInvocationHandler在invoke里先检查方法上是否有幂等注解有的话就从参数里提取唯一标识去缓存里查是否已存在。如果存在就拒绝执行不存在则执行目标方法并写入缓存。这样业务代码只需要加一个注解不需要关心幂等的具体实现。后来这个方案被推广到了好几个项目组大家都觉得比在每个方法里手写校验逻辑清爽得多。5.4 性能开销与优化建议动态代理不是没有代价的。JDK 代理的方法调用走的是反射比直接调用慢CGLIB 虽然调用快但创建代理类的过程比较耗时。在高频调用场景下这些开销会被放大。我的优化建议是代理对象尽量复用不要每次调用都重新创建。如果目标方法调用极其频繁考虑在代理层加缓存减少真实方法的执行次数。对于性能敏感的核心链路可以评估是否真的需要代理有时候直接写代码反而更合适。使用 CGLIB 时注意设置合理的缓存策略避免重复生成代理类导致内存膨胀。6. 常见问题排查与避坑指南6.1 代理不生效的几种典型情况在实际开发中代理不生效是最常见的问题。我整理了一个速查表现象可能原因排查方向事务不生效同类内部调用检查是否通过 this 调用日志不打印方法不是 public检查方法修饰符代理类转换失败类加载器不一致检查类加载器来源CGLIB 报错目标类是 final检查类和方法是否 final注解不生效代理模式不匹配检查是否强制使用 JDK 代理6.2 代理对象与原始对象的区分有时候你需要拿到原始对象而不是代理对象。比如在做单元测试时你想直接调用真实方法绕过代理逻辑。如果是 Spring 环境可以通过AopProxyUtils.getSingletonTarget()获取原始对象。如果是自己写的代理那就简单了直接持有原始对象的引用即可。但要注意不要随意把代理对象和原始对象混用否则可能出现逻辑不一致的问题。我见过一个案例有人在缓存里存了原始对象在数据库操作时用了代理对象结果缓存更新和事务提交不同步导致数据不一致。6.3 序列化与代理对象的兼容问题代理对象在序列化时经常会出问题。因为代理类是在运行期生成的没有对应的 class 文件反序列化时可能找不到类定义。解决办法有两个一是让代理类实现Serializable接口并确保类加载器一致二是在序列化前先取出原始对象序列化原始对象而不是代理对象。我在做分布式缓存的时候就遇到过这个问题后来统一改成序列化原始对象问题就消失了。6.4 调试代理代码的实用技巧代理代码的调试比较麻烦因为你看不到生成的代理类长什么样。有两个办法可以解决第一个是设置系统属性jdk.proxy.ProxyGenerator.saveGeneratedFilestrue这样 JDK 会把生成的代理类字节码保存到本地你可以用反编译工具打开看。第二个是使用 Arthas 这类诊断工具直接在线查看代理类的结构和方法调用链路。这个在排查线上问题时特别有用。注意保存代理类文件会占用磁盘空间只在调试时开启生产环境记得关闭。7. 从面试题到真实能力的跨越7.1 面试常问的几个点关于动态代理面试官最喜欢问的无非这几个JDK 代理和 CGLIB 代理的区别、动态代理的实现原理、Spring AOP 用的是哪种代理、为什么 JDK 代理必须基于接口。这些问题本身不难但如果你只背答案很容易被追问到卡壳。比如面试官接着问“那你说说 JDK 代理生成的代理类它的结构是什么样的”如果你没看过生成的字节码就答不上来了。我的建议是不要只停留在会用 API 的层面找机会把生成的代理类反编译出来看看理解它的字段、构造方法、方法实现。看过一次比背十遍答案都管用。7.2 从会用到底层理解的路径如果你想把动态代理真正吃透我建议按这个顺序深入先能手写 JDK 代理和 CGLIB 代理的完整示例。反编译生成的代理类理解它的结构。阅读Proxy类和InvocationHandler的源码。了解 ASM 字节码框架的基本用法。阅读 Spring AOP 和 MyBatis 中代理相关的源码。走到第三步你对动态代理的理解就已经超过大部分开发者了。走到第五步你就能在框架层面自由定制代理逻辑解决更复杂的问题。7.3 我个人的学习体会说实话我当初学动态代理的时候也是背了很多面试题但真正让我开窍的是一次线上问题排查。当时一个事务莫名其妙不回滚查了半天才发现是内部调用导致代理失效。从那以后我对代理的理解就不再是纸上的概念而是变成了实实在在的经验。技术这东西看再多不如动手写一次写一次不如踩一次坑。动态代理如此其他技术也是如此。希望这篇内容能帮你少走一些弯路至少在我踩过的那些坑上你可以直接绕过去。最后分享一个小技巧如果你在项目里需要写代理逻辑不妨先问自己一个问题——这个逻辑是不是所有方法都需要如果只有部分方法需要那用注解加条件判断会比无差别代理更清晰。代理是工具不是目的用对了地方才有价值。

相关新闻

用Matplotlib绘制3D响应面图:从实验数据到SCI级曲面可视化

用Matplotlib绘制3D响应面图:从实验数据到SCI级曲面可视化

搞实验优化的人,手头一定都攒过类似的数据:因素A换几个水平、因素B换几个水平,跑出来一个响应值Y,一共26组,Excel里排得整整齐齐。可一到了写论文,这张表格总不能直接甩给审稿人看,你需要的是一…

2026/10/11 5:03:08 阅读更多 →
电影票订票小程序完整源码:选座锁座与订单状态机实战

电影票订票小程序完整源码:选座锁座与订单状态机实战

简介:这是一套面向高校计算机相关专业学生与Java初学者的小程序电影票订票系统完整源码,可作为毕业设计、课程设计或全栈练手项目。项目采用SSM框架搭建后台,前端为uni-app/原生小程序,配套MySQL 5.7数据库,覆盖影院推…

2026/10/11 8:31:46 阅读更多 →
别再逼测试学Python:低代码测试才是测试团队的正解

别再逼测试学Python:低代码测试才是测试团队的正解

前阵子有位测试主管跟我说,他们组的新人培训计划又加了二十节Python课,结果三个月过去,能独立写脚本的只有两个人,剩下的全在用CtrlC和CtrlV“续命”。类似的场景我见了太多次。所以今天聊一个可能有点得罪人的观点:别…

2026/10/11 8:31:50 阅读更多 →

最新新闻

Apache Beam Calcite SQL 标量函数完全指南:从比较运算到日期/字符串处理的完整参考

Apache Beam Calcite SQL 标量函数完全指南:从比较运算到日期/字符串处理的完整参考

【免费下载链接】beam Apache Beam is a unified programming model for Batch and Streaming data processing. 项目地址: https://gitcode.com/gh_mirrors/beam18/beam 点击查看 免费下载 Apache Beam 的 Calcite SQL 方言(Beam Calcite SQL&#xff…

2026/10/11 9:56:54 阅读更多 →
哈希表(计数)— 赎金信

哈希表(计数)— 赎金信

文章目录一、[题目](https://leetcode.cn/problems/ransom-note/description/?envTypestudy-plan-v2&envIdtop-interview-150)二、My thinking三、哈希表3.1 思路3.2 算法步骤3.3 代码实现3.4 时间和空间复杂度四、总结一、题目 给你两个字符串:ransomNote 和…

2026/10/11 9:56:54 阅读更多 →
DeepSeek大模型本地部署与性能优化实战指南

DeepSeek大模型本地部署与性能优化实战指南

简介:本资源是一份面向开发者与AI技术爱好者的DeepSeek大模型本地化实践指南,聚焦解决大模型部署门槛高、硬件适配难、运行成本高等实际问题。内容覆盖从国产大模型选型优势分析、多档位硬件配置推荐(含RTX 3060至4090的显存/内存/性价比方案…

2026/10/11 9:56:54 阅读更多 →
北京24小时自助健身房系统开发实战指南与案例解析

北京24小时自助健身房系统开发实战指南与案例解析

北京 24 小时自助健身房系统开发实战指南 随着城市生活节奏的加快,健身需求日益增长,24 小时自助健身房成为一种高效、便捷的解决方案。在北京这样的大城市,用户对健身时间的灵活性和设备的智能化提出了更高要求。因此,开发一套稳…

2026/10/11 9:56:54 阅读更多 →
BetterCapture双音轨录屏教程:系统声音+麦克风分轨录制,后期剪辑自由调音量

BetterCapture双音轨录屏教程:系统声音+麦克风分轨录制,后期剪辑自由调音量

【免费下载链接】BetterCapture The macOS screen recorder for the rest of us - always free and open source with a native look and feel 📺 项目地址: https://gitcode.com/gh_mirrors/be/BetterCapture 点击查看 免费下载 如果你录屏时经常遇到&…

2026/10/11 9:56:53 阅读更多 →
AI-For-Beginners 实战:用卷积神经网络实现宠物品种分类(Pet Faces 实验室)

AI-For-Beginners 实战:用卷积神经网络实现宠物品种分类(Pet Faces 实验室)

教程人工智能机器学习深度学习 【免费下载链接】AI-For-Beginners 12 Weeks, 24 Lessons, AI for All! 项目地址: https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners 点击查看 免费下载 本篇文章基于 AI-For-Beginners 课程中「卷积神经网络(07…

2026/10/11 9:55:53 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →