代理模式这一块几乎每次 Java 面试都能碰到从静态代理到 JDK 动态代理再到 CGLIB 动态代理层层递进。很多同学平时写业务代码时也用过但一被问到底层原理就卡壳。这篇内容我按“从问题出发、再层层拆解”的思路把代理模式的核心原理、两类动态代理的实现细节以及它们在 Spring AOP 这类真实场景中的选型逻辑一次讲透。如果你正在准备 Java 面试或者工作一两年后发现自己的基础还不够扎实那这篇内容基本就是给你准备的。它不光是能应付“Java 面试八股文”里的高频题更重要的是理解动态代理之后你再去看 Spring 的声明式事务、MyBatis 的 Mapper 接口、RPC 框架的透明调用都会有一种“原来如此”的通透感。1. 代理模式到底在解决什么问题1.1 先从生活场景认识“代理”理解一个设计模式最好的方式就是先找生活中的对应物。代理模式在生活中最常见的就是房产中介你想租房但你没有时间逐个房源去联系房东、约时间、看房、处理合同于是你委托一个中介。中介替你完成筛选房源、预约看房、协商价格这些事而你真正关心的核心动作“签合同、入住”仍然是你和房东之间完成的。中介没有改变房屋本身只是在房屋和你之间加了一层“服务”。映射到 Java 里目标对象就相当于房东代理对象就相当于中介而调用方你只跟代理对象打交道。代理对象在转发调用之前或之后可以加入各种附加逻辑比如筛选、记录日志、权限校验、性能统计这些逻辑被称为横切逻辑。代理模式的官方定义也很简单给某个对象提供一个替身通过这个替身去控制对原对象的访问。这个替身就叫代理对象原对象叫目标对象。为什么要绕一层因为很多时候我们不想直接改目标对象的代码又想在调用目标方法前后做一些额外的处理这个时候代理就是最优雅的插桩方式。1.2 三类角色搞懂代理结构就成功了一半代理模式的参与者就三个Subject抽象主题定义真实对象和代理对象共有的业务接口调用方依赖这个接口编程。RealSubject真实主题真正干活的对象也就是目标对象。Proxy代理持有 RealSubject 的引用在调用 RealSubject 的方法前后插入增强逻辑。画成类图很直观但我不画了你记住一句话代理类和目标类实现同一个接口代理类里面包了一个目标类对象。调用方拿到的永远是代理对象但代理最终会把请求转发给目标对象。这个结构在静态代理和 JDK 动态代理里都一样区别只在于代理类是手写的还是运行时生成的。1.3 静态代理示例一个最简单的可复现代码先看一个手写的静态代理例子体会最原始的做法。场景是发送通知消息目标是记录日志后再发消息。// 抽象主题定义业务接口 public interface Notifier { void send(String message); } // 真实主题真正发消息的实现 public class SmsNotifier implements Notifier { Override public void send(String message) { System.out.println(【短信服务】发送消息: message); } } // 代理在发送前后加日志增强 public class NotifierProxy implements Notifier { private final Notifier target; public NotifierProxy(Notifier target) { this.target target; } Override public void send(String message) { System.out.println([静态代理] 准备发送短信...); target.send(message); System.out.println([静态代理] 短信发送完成已记录日志); } } // 客户端使用 public class Client { public static void main(String[] args) { Notifier notifier new NotifierProxy(new SmsNotifier()); notifier.send(你的验证码是 8866); } }这段代码的逻辑非常清晰客户端创建了一个 NotifierProxy这个代理内部包了一个 SmsNotifier。调用 send 方法时实际执行的是代理里的增强逻辑执行完之后再转发给目标类。输出结果是“准备发送短信”日志在前“短信发送完成”日志在后中间夹着目标对象的真实输出。1.4 静态代理的三个痛点催生了动态代理刚才这个静态代理有没有问题有而且问题很直观。第一个痛点是类爆炸。假设系统里有短信通知、邮件通知、站内信通知三个实现类每个都要加日志、加权限校验、加耗时统计那就得写三个代理类如果以后业务类型扩到十个代理类也跟着变成十个。代码体积越来越大维护成本直线上升。第二个痛点是增强逻辑重复。日志、校验、统计这类逻辑本质上和业务无关却被复制粘贴到了所有代理类里。改一个日志格式所有代理类都得跟着改漏掉一个就出事故。第三个痛点是编译期绑定。代理类是手写的意味着代码在编译前就已经确定死了。如果运行时才知道目标对象是谁或者目标对象本身是动态加载进来的静态代理就没法玩了。这三个痛点合在一起就引出了动态代理的需求能不能在运行的时候再生成代理类能不能一个代理处理器搞定所有目标对象这就是 JDK 动态代理和 CGLIB 动态代理存在的根本原因。2. JDK 动态代理代码与原理一起看2.1 让 JDK 在运行时替你生成代理类JDK 动态代理是 Java 官方提供的方案它利用反射和字节码生成技术在运行时创建代理类。核心就两个类InvocationHandler 接口和 Proxy 类。InvocationHandler 是一个处理器接口里面只有一个方法public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }三个参数含义分别是proxy生成的代理对象本身一般用不到但调用时会传进来。method当前被调用的方法对应的 Method 对象通过它可以拿到方法名、参数类型、注解等元数据。args调用方法时传入的参数数组没有参数就是 null。Proxy 类负责创建代理对象最核心的方法是 newProxyInstancepublic static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h) // 三个参数类加载器、目标对象实现的接口数组、处理器用前面那个发短信的例子改造成 JDK 动态代理感受一下差别// 目标类继续用之前的 SmsNotifier不需要写代理类 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 { System.out.println([JDK动态代理] 方法执行前记录日志: method.getName()); Object result method.invoke(target, args); System.out.println([JDK动态代理] 方法执行后记录日志完成); return result; } } // 客户端代码 public class Client { public static void main(String[] args) { Notifier target new SmsNotifier(); Notifier proxy (Notifier) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target)); proxy.send(你的验证码是 8866); } }注意看这一版里没有写 NotifierProxy 类只有一个 InvocationHandler 的实现。不管你有几个接口、几个目标类这套增强逻辑只需要写一遍。以后就算新增了邮件通知、站内信通知只要改一行目标对象其他代码全都复用。2.2 invoke 方法里的路由逻辑很多同学第一次看动态代理最大的疑惑是这个 invoke 是怎么知道当前调用的是哪个方法的其实原理不神秘。JDK 在运行时会生成一个代理类这个代理类实现了你传入的所有接口接口里的每个方法都会被代理类重写。当你调用 proxy.send(验证码) 时执行的不是 SmsNotifier 的 send而是代理类里的 send。代理类里的 send 方法内部把这次调用包装成一个 Method 对象连同参数一起丢给 InvocationHandler 的 invoke 方法。所以 invoke 方法本质上是一个“总入口”所有接口方法的调用都会汇聚到这里。你可以在 invoke 里根据 method.getName() 判断当前是哪个方法然后决定要不要增强、增强什么逻辑Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (send.equals(method.getName())) { System.out.println([日志] 发送消息开始); } Object result method.invoke(target, args); System.out.println([日志] 方法执行完成: method.getName()); return result; }这种按方法名路由的写法在 MyBatis 的 MapperProxy 里非常常见。MyBatis 的 Mapper 接口没有实现类所有方法调用都会走进 MapperProxy.invoke然后根据方法名和参数拼接出 SQL 的 key再从 SqlSession 里执行对应的 SQL 语句。你要想理解那些“接口没有实现类为什么能调用”的框架核心就是这里。2.3 Proxy.newProxyInstance 底层到底生成了什么东西很多人能写代码但一被问到“JDK 动态代理原理”就答不上来。我建议你从生成代理类的角度去理解思路一下就通了。Proxy.newProxyInstance 的底层大概分三步第一步根据传入的接口数组调用 ProxyClassFactory 在运行时生成一个代理类的字节码这个代理类叫 $Proxy0实现了你传入的所有接口并且继承自 java.lang.reflect.Proxy。类加载器把生成的字节码加载进 JVM。第二步代理类的构造方法接收一个 InvocationHandler 参数这个构造方法来自父类 Proxy 的 protected 构造方法。Proxy 类里有一个字段protected InvocationHandler h;代理类构造时会把你的 LogInvocationHandler 赋值给这个字段。第三步通过反射拿到代理类的构造器传入 InvocationHandler 创建实例。如果你想亲眼看一下 JDK 生成的代理类长什么样可以这样System.setProperty(sun.misc.ProxyGenerator.saveGeneratedFiles, true); // JDK 8 用上面JDK 9 改成 System.setProperty(jdk.proxy.ProxyGenerator.saveGeneratedFiles, true);然后在代码里生成代理对象再去项目根目录找 com/sun/proxy/$Proxy0.class 文件反编译之后大致长这样public final class $Proxy0 extends Proxy implements Notifier { private static Method m1; // equals private static Method m2; // toString private static Method m3; // send private static Method m0; // hashCode public $Proxy0(InvocationHandler h) { super(h); } public final String toString() { return (String) super.h.invoke(this, m2, null); } public final int hashCode() { return (Integer) super.h.invoke(this, m0, null); } public final boolean equals(Object obj) { return (Boolean) super.h.invoke(this, m1, new Object[]{obj}); } public final void send(String message) { try { super.h.invoke(this, m3, new Object[]{message}); } catch (RuntimeException | Error e) { throw e; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m3 Class.forName(Notifier).getMethod(send, String.class); // 其他方法类似 } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } }这个反编译代码看完就全明白了所谓的 JDK 动态代理实际上是在运行时生成一个实现了业务接口、继承了 Proxy 的新类。所有接口方法都被重写方法体里统一调用 super.h.invoke。h 就是那个 InvocationHandler。另外注意 m1、m2、m3 这些 static 字段它们在静态代码块里通过反射一次性缓存了 Method 对象这样每次调用不用重新查找方法性能上有保障。2.4 为什么 JDK 动态代理必须基于接口这是面试里的高频追问答案只有一个核心Java 是单继承。JDK 生成的代理类已经继承了 Proxy 类所以它不可能再继承你的目标类只能通过实现接口的方式来扩展能力。如果你的目标类没有实现任何接口JDK 动态代理就没有用武之地因为代理类无法确定该重写哪些方法。这也是 CGLIB 动态代理存在的意义。CGLIB 不要求目标类实现接口它直接在运行时生成目标类的子类通过继承的方式来扩展。至于 CGLIB 的细节和局限下一节详细说。3. CGLIB 动态代理没接口时的救星3.1 CGLIB 是什么CGLIB 的全称是 Code Generation Library它底层基于 ASM 字节码操作框架可以在运行时生成目标类的子类。因为用的是继承机制所以目标类有没有接口都无所谓只要目标类不是 final、方法不是 final就能通过 CGLIB 做代理。Spring 早期的版本里如果目标类没有实现接口就会自动切换到 CGLIB。那时的 CGLIB 需要额外引入依赖因为 JDK 本身不内置。到了 Spring Boot 2.x 之后的版本Spring 默认就直接使用 CGLIBASM 封装为 Spring 内部的 SpringObjenesis来生成代理了这也是为什么现在很多项目里哪怕某个类实现了接口生成的代理对象也不是 JDK 动态代理而是 CGLIB 代理。3.2 一个最小可运行示例用 CGLIB 改造前面的发短信例子注意这次我故意不写接口直接代理实现类首先引入依赖以 Maven 为例dependency groupIdcglib/groupId artifactIdcglib/artifactId version3.3.0/version /dependency然后写目标类和代理逻辑// 目标类没有实现任何接口 public class EmailSender { public void send(String to, String content) { System.out.println(【邮件服务】发给 to 内容: content); } } // 代理逻辑 public class LogInterceptor implements MethodInterceptor { Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println([CGLIB] 方法执行前: method.getName()); Object result proxy.invokeSuper(obj, args); System.out.println([CGLIB] 方法执行后); return result; } } // 客户端 public class Client { public static void main(String[] args) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(EmailSender.class); enhancer.setCallback(new LogInterceptor()); EmailSender proxy (EmailSender) enhancer.create(); proxy.send(xiaoyuexample.com, Hello Dynamic Proxy); } }关键点有几个Enhancer 是 CGLIB 的入口类setSuperclass 指定目标类setCallback 指定拦截器。intercept 方法有四个参数obj 是代理对象本身method 是被拦截的方法args 是参数MethodProxy 是 CGLIB 提供的方法代理它内部做了性能优化。这里调用的是 proxy.invokeSuper(obj, args)而不是 method.invoke(target, args)这一行是有讲究的后面详说。3.3 从字节码和 FastClass 角度理解 CGLIBCGLIB 生成的代理类是目标类的子类所以代理类里会重写父类的所有非 final、非 private 方法。调用时代理对象的入口是重写后的方法这些方法内部会调用拦截器的 intercept 方法。和 JDK 动态代理每个方法调用都走反射不同CGLIB 引入了一个 FastClass 机制来提升效率。所谓 FastClass就是给目标类生成一个索引表方法调用不再通过 Java 反射去查找 Method而是直接通过整数索引定位方法然后调用。这就是为什么 MethodProxy.invokeSuper 的速度在实际场景中往往比 JDK 动态代理的 method.invoke 更快。这里有一个非常容易踩的坑在 intercept 方法里如果用 method.invoke(obj, args) 而不是 proxy.invokeSuper(obj, args)会出现递归调用最终栈溢出。原因很简单obj 是 CGLIB 生成的代理对象method.invoke 传入 obj 时实际上调用的是代理对象重写后的方法而代理对象重写后会再次触发 intercept于是无限递归。正确做法是调用 proxy.invokeSuper它通过 FastClass 直接定位到父类的原始方法去执行。3.4 CGLIB 的边界和局限CGLIB 虽然不用接口了但它有自己的硬性限制面试里经常一环扣一环地问主要注意下面几点第一目标类不能是 final 类。因为 CGLIB 要继承它生成子类final 类无法被继承直接报错。第二final 方法和 private 方法不能被拦截。final 方法在子类里不能重写CGLIB 没法拦截private 方法只能在本类内调用子类根本看不到也拦截不了。第三方法不能是 static。静态方法属于类不属于实例子类无法重写CGLIB 拦截不了。第四如果你在目标类的构造方法里调用了一个可被代理的方法CGLIB 在创建代理对象时会触发拦截器逻辑这个阶段可能会造成意想不到的副作用。这个问题在 Spring AOP 里也专门有一章讨论构造器调用和代理初始化的顺序。4. 真实项目里的动态代理Spring AOP 与其他经典场景4.1 两大动态代理对比一张表看懂差别面试官问你“JDK 动态代理和 CGLIB 动态代理有什么区别”如果你能说出下面这张表的核心内容基本就过关了对比维度JDK 动态代理CGLIB 动态代理底层机制运行时生成接口实现类运行时生成目标类的子类目标对象要求必须实现接口无接口要求但不能是 final 类方法拦截方式反射调用目标方法FastClass 索引调用父类方法生成代理类速度较快较慢需要生成子类字节码方法调用性能反射调用相对慢索引调用性能较好版本依赖JDK 内置需要额外依赖 cglib 包适用场景接口驱动的设计非接口类代理补充一个很有意思的细节JDK 动态代理生成的代理类是在运行时动态生成的但它也是“类”也要走类加载流程。如果你用 -verbose:class 启动能看到 $Proxy0 被加载的日志。CGLIB 生成的代理类名长这样目标类名$$EnhancerByCGLIB$$一串随机数。4.2 Spring AOP 默认选型逻辑与变化趋势Spring AOP 在选择 JDK 动态代理还是 CGLIB 时有自己的一套策略而且这个策略在 Spring Boot 的不同版本里还有变化。在老版本的 Spring包括 Spring Boot 1.x里默认逻辑是这样如果目标对象实现了接口优先使用 JDK 动态代理只有目标对象没有实现接口时才退回到 CGLIB。从 Spring Boot 2.x 开始官方把默认策略改成了 CGLIB。也就是说即使你的类实现了接口Spring 也直接生成 CGLIB 子类代理不再走 JDK 动态代理。这个调整的好处是统一了行为避免了因为接口和实现混用导致的一些边界问题缺点是 CGLIB 创建代理对象的开销更大而且对 final 方法天然不敏感。如果你就是想让 Spring Boot 2.x 的 AOP 回到 JDK 动态代理可以通过 spring.aop.proxy-target-classfalse 来调整。但我的建议是不要为了追求理论上的性能而去切配置Spring 默认的东西已经经过大量生产环境验证保持默认就好。Spring AOP 底层用的是 ProxyFactory它内部组合了两种代理策略如果 proxy-target-class 为 true用 CGLIB否则检查目标对象的接口情况。核心思想就一句话有接口优先 JDK没有接口一定 CGLIB这是理解 Spring AOP 的关键。4.3 延伸场景MyBatis Mapper、RPC 框架、声明式事务理解动态代理之后你再看很多框架源码会特别顺畅。先说 MyBatis。MyBatis 的 Mapper 接口没有实现类你却能直接注入一个 Mapper 接口的代理对象并调用方法靠的就是 JDK 动态代理。在 MapperProxy.invoke 方法里它会根据当前方法名、参数类型从 MapperMethod 里找到对应的 SQL然后交给 SqlSession 执行最后把结果集映射成返回类型。所以你在接口上写一个 Select 注解方法调用就能映射到 SQL 执行这个魔法背后就是动态代理。再说 RPC 框架。像 Dubbo 这类框架里消费者端的服务接口本地没有实现类调用远程服务就像调用本地方法一样。核心原理就是动态代理接口的方法调用被代理到某个处理器中处理器把方法名、参数序列化后通过网络发送给服务提供方再反序列化返回结果。这个透明调用的体验完全依赖动态代理。还有 Spring 的声明式事务。Transactional 注解为什么有效因为在被标记的方法上Spring 已经用代理对象包装了你的业务 Bean。代理对象在调用业务方法前会开启事务方法执行完之后根据是否抛异常决定提交还是回滚。如果你在同一个类里通过 this 调用另一个带 Transactional 的方法由于 this 是原始对象而不是代理对象事务注解就不会生效这就是经典的“自调用事务失效”问题。5. 面试高频题与踩坑排查经验5.1 一道经典面试题JDK 和 CGLIB 谁更快这个问题很容易回答翻车因为答案是分阶段的。如果比较生成代理对象的速度JDK 动态代理更快。因为 JDK 只需要生成一个实现接口的类结构简单CGLIB 需要通过字节码生成子类涉及的方法更多构造过程更复杂。但如果比较调用代理方法的速度在绝大多数实际场景里CGLIB 是更快的。核心原因是 CGLIB 用了 FastClass 机制方法调用通过索引直接定位避开了纯反射的方法查找开销。JDK 动态代理虽然也对 Method 做了缓存但每次 invoke 还是经过反射。不过这个性能差距在业务代码的常规调用量下几乎可以忽略不计真正的瓶颈都在 I/O 和数据库访问上。面试时能说出来“JDK 创建快、CGLIB 调用快、但常规场景差距微乎其微”就是满分答案。5.2 常见问题速查表我把实际开发里最常见的几个代理相关问题整理成了表格方便遇到对应报错时快速定位现象原因解决办法java.lang.ClassCastException 无法强转代理对象强制类型用的不是接口类型而是目标实现类类型代理对象要用接口类型接收或者改成 CGLIB 代理CGLIB 报无法继承 final 类目标类是 final 修饰去掉 final在 Spring 中换成 JDK 动态代理方法没走增强逻辑日志没打印方法可能是 final/static/private无法被代理去掉 final/static或者调整代理方式Transactional 同一个类中 this 调用不生效this 指向原始对象而非代理对象通过注入自身代理对象调用或拆到另一个 Bean无限递归 StackOverflowErrorCGLIB 拦截器里用了 method.invoke(obj)改成 proxy.invokeSuper(obj, args)aop 配置了但接口实现类没被增强目标方法是通过 final class 提供的实现改为非 final或用 CGLIB 代理5.3 我的三条避坑心得第一业务代码里拿到的到底是 JDK 代理还是 CGLIB 代理不能靠猜。如果你发现注入的对象 Class 是 $Proxy 开头的说明走了 JDK如果是 $$EnhancerByCGLIB$$ 开头的说明是 CGLIB。排查 AOP 不生效这类问题时先打印一下对象的 getClass().getName() 确认代理是否生成这一步能过滤掉一半的无效排查。第二代理对象的 equals、hashCode、toString 也同样会被拦截。JDK 代理类的 Object 三个方法在静态代码块里被缓存的 m0、m1、m2 所对应调用这三个方法时也会走进 InvocationHandler.invoke。如果你的增强逻辑里有诸如“方法名是 toString 就返回固定字符串”这种操作那真实对象的 toString 就永远不会调用了。所以写 InvocationHandler 时一定要对 Object 方法做好放行或处理的逻辑。第三不要把代理对象存到缓存后跨线程共享。CGLIB 代理对象不是线程不安全的但代理对象可能绑定了一些初始化上下文特别是在 Spring 的代理对象里如果它在第一次访问时才完成目标 Bean 的初始化跨线程使用时容易出现奇怪的问题。我建议代理对象的创建和使用保持在同一线程模型内真需要共享就给代理对象加个缓存池避免重复创建。代理模式和动态代理这块内容我把它们完整拆开已经讲得比较透了。你再回去看 Spring AOP、MyBatis Mapper、RPC 的源码会发现那些“神奇”的框架能力本质上都是“动态生成一个类把所有方法调用拦下来做点事情再转发”这个套路。我个人在实际工作中最大的体会是与其死记八股文不如自己动手写一个 InvocationHandler再打印一下生成的代理类看看字节码长什么样。跑通一次原理比背十道面试题都管用。遇到接口没实现却能被注入的例子往这个方向想基本不会错。
企业数字化 ERP 产品动态
相关推荐
高架视角车辆检测数据集:600张图三格式标签与YOLO11一键训练脚本 简介:这份资源面向交通监控与自动驾驶感知方向的算法开发者,提供高架视角下的道路车辆检测数据集,覆盖城市道路、高速道路、农村道路及车辆遮挡、严重遮挡等真实场景,统一标注为 Vehicle 单一类别,适合作为车辆检测项目… · 2026/9/24 23:11:44
Vue3入门指南:从createApp到单文件组件,彻底搞懂项目结构 很多刚接触 Vue3 的人,第一反应往往不是“它好用”,而是“它到底是个啥”。尤其是你从一个 Vue2 老项目切过来,打开新项目一看,入口不再是new Vue({ el: #app }),而是createApp(App).mount(#app);页面文件变… · 2026/9/24 23:11:44
非标设备联网:破解传统运维难题,实现预测性维护 前阵子去一家做汽车零部件的工厂,车间主任指着刚刚恢复运行的一台定制装配设备说:“这次算是运气好,两个小时就找到了故障点。上个月同样的问题,前前后后折腾了一整天。”这句话让我印象很深,因为“运气好”这三个字&a… · 2026/9/24 23:11:44
Reference 项目 Lua 5.4 速查表:从基础语法到表、元表与文件 IO 的完整实战指南 Reference 项目 Lua 5.4 速查表:从基础语法到表、元表与文件 IO 的完整实战指南 【免费下载链接】reference ⭕ Share quick reference cheat sheet for developers. 项目地址: https://gitcode.com/gh_mirrors/re/reference
本篇技术指南以 Reference 开源速… · 2026/9/24 23:53:42
TCP端口为什么是65535?从16位字段到实践排查全解析 1. 从一道“送命题”说起做网络开发、运维或者后端服务的同学,几乎都遇到过这样一幕:面试官漫不经心地问一句“TCP/UDP端口的范围为什么是0到65535,总共65536个?为什么不是65535个?”——注意,这里已经有一… · 2026/9/24 23:53:42
ESP32与W5500 SPI通信失败的三大时序根源解析 1. 为什么你写的 SPI 总是“通不了”?——从 ESP32 和 W5500 的握手失败说起我第一次把 ESP32 和 W5500 焊上 PCB 板,通电后串口打印出W5500 init failed的那一刻,盯着那行红字看了足足三分钟。不是没接线,不是没供电,… · 2026/9/24 23:53:42
ESP32上运行WebAssembly:原理、解释器与实战指南 1. 从“鸡同鸭讲”到“同声传译”:先弄清CPU和WASM到底什么关系先抛一个反直觉的结论:ESP32的CPU不认识WebAssembly,但ESP32上跑的“翻译官”认识,而且这个翻译官本身是一段CPU认识的机器码。很多刚接触WASM(WebAssemb… · 2026/9/24 23:53:42
ESP32-C5-WROOM-1U双频Wi-Fi 6模块:从原理到实战的选型指南 如果你在智能家居、工业物联网或者音视频传输领域做产品选型,这两年一定被一个问题反复折磨:手上这颗Wi-Fi芯片,到底要不要上双频?单频方案便宜够用,但2.4GHz频段越来越挤;双频方案性能好,可在E… · 2026/9/24 23:53:35
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44