当前位置: 首页 > news >正文

【Spring之手写一个依赖注入容器】

Spring之手写一个依赖注入容器

  • 1. 创建两个自定义注解
    • 1.1 Component注解
    • 1.2 DI注解
  • 2. ApplicationContext接口与实现类
    • 2.1 ApplicationContext 接口
    • 2.2 实现类:DefaultListableApplicationContext
  • 3. 定义DAO层和Service层及其实现
  • 4. 定义异常信息类
    • 4.1 InjectBeanException
    • 4.2 NotExistsBean
    • 4.3 NotSupportMoreSuperInterface
  • 5. 测试自定义bean容器(带依赖注入)
    • 5.1 新建测试类TestUser
    • 5.2 输出结果

1. 创建两个自定义注解

1.1 Component注解

@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {String value() default "";
}

1.2 DI注解

@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DI {String value() default "";}

2. ApplicationContext接口与实现类

2.1 ApplicationContext 接口

public interface ApplicationContext {// 获取beanObject getBean(String name);// 获取bean<T> T getBean(String name, Class<T> clazz);// 判断bean是否存在boolean exists(String name);boolean exists(String name, Class<?> clazz);
}

2.2 实现类:DefaultListableApplicationContext

// 实现类
public class DefaultListableApplicationContext implements ApplicationContext {/*** 存放bean对象容器*/private Map<String, Object> objectsMap = new ConcurrentHashMap<>(128);private Map<Class, Object> objectsTypeMap = new ConcurrentHashMap<>(128);private Logger log = LoggerFactory.getLogger(DefaultListableApplicationContext.class);// 扫描的基础路径private String basePath;public DefaultListableApplicationContext(String basePackage) {//public static void pathDemo1(String basePackageName){String packagePath = basePackage.replaceAll("\\.", "\\\\");try {URL url = Thread.currentThread().getContextClassLoader().getResource(packagePath);if (StringUtils.hasText(url.getPath())) {String filePath = URLDecoder.decode(url.getFile(), "UTF8");// 扫描的基础路径basePath = filePath.substring(0, filePath.length() - packagePath.length());//包扫描loadBeans(new File(filePath));// 依赖注入loadDI();}} catch (IOException e) {throw new RuntimeException(e);}}@Override@Nullablepublic Object getBean(String name) {return objectsMap.get(name);}@Override@Nullablepublic <T> T getBean(String name, Class<T> clazz) {Object obj = objectsMap.get(name);if (obj == null) {Object bean;if ((bean = objectsTypeMap.get(clazz)) == null) {for (Class<?> interfaceClazz : clazz.getInterfaces()) {if ((bean = objectsTypeMap.get(interfaceClazz)) != null)return (T) bean;getBean(name, interfaceClazz);}return null;}return (T) bean;}return clazz.isInstance(obj) ? clazz.cast(obj) : null;}@Overridepublic boolean exists(String name) {return objectsMap.get(name) == null ? false : true;}@Overridepublic boolean exists(String name, Class<?> clazz) {if (objectsMap.get(name) == null) {if (objectsTypeMap.get(clazz) == null) {for (Class<?> interfaceClazz : clazz.getInterfaces()) {if (objectsTypeMap.get(interfaceClazz) != null)return true;exists(name, interfaceClazz);}return false;}return true;}return true;}/*** 包扫描** @param file*/private void loadBeans(File file) {// 1 判断当前文件是否文件夹if (file.isDirectory()) {File[] childrenFiles = file.listFiles();// 2. 获取文件夹里面的所有内容// 3. 判断文件夹为空,直接返回if (childrenFiles == null || childrenFiles.length == 0) {return;}// 4. 如果文件夹不为空,遍历文件夹内容for (File childFile : childrenFiles) {// 4.1  遍历得到每个file对象,继续判断。如果是文件夹,递归if (childFile.isDirectory()) {loadBeans(childFile);} else {// 4.2 遍历得到的file不是文件夹,是文件// 4.3 得到包路径 + 类名称部分// C:/desktop/IdeaProjects/spring-mytest/src/main/java/com/ypy/contextString pathWithClass = childFile.getAbsolutePath().substring(basePath.length() - 1);// 4.4 判断当前文件类型是否为.classif (pathWithClass.endsWith(".class")) {// 4.5 如果是.class类型,把路径\替换成., 把.class去掉// com.ypy.context.beans.UserServiceImplString allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");// 4.6 使用反射实例化try {Class<?> clazz = Class.forName(allName);if (!clazz.isInterface()) {// 4.6.1 判断是否有@Component注解,有if (clazz.getAnnotation(Component.class) != null) {Object instance = clazz.getConstructor().newInstance();// 优先使用用户自定义的bean名称if (StringUtils.hasText(clazz.getAnnotation(Component.class).value())) {String beanName = clazz.getAnnotation(Component.class).value();objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");if (clazz.getInterfaces().length == 1) {objectsTypeMap.put(clazz.getInterfaces()[0], instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");} else if (clazz.getInterfaces().length == 0) {objectsTypeMap.put(clazz, instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");} else {throw new NotSupportMoreSuperInterface("Not support the bean that has more than two super interfaces.");}}// 其次使用父接口的类名作为bean名称else if (clazz.getInterfaces().length > 0) {String interfaceName = clazz.getInterfaces()[0].getSimpleName();String beanName = lowercaseFirstLetter(interfaceName);// 放入容器中objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");// 可能出现bugobjectsTypeMap.put(clazz.getInterfaces()[0], instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");}// 如果说没有父接口,就使用类名作为bean名称else {String beanName = lowercaseFirstLetter(clazz.getSimpleName());// 放入容器中objectsMap.put(beanName, instance);log.warn(">>> objectsMap store bean(name,obj) ===> (" + beanName + "," + instance + ")");objectsTypeMap.put(clazz, instance);log.warn(">>> objectsTypeMap store bean(class,obj) ===> (" + clazz.getInterfaces()[0].getSimpleName() + "," + instance + ")");}}}} catch (Exception e) {throw new RuntimeException(e);}}}}}}private void loadDI() {for (Map.Entry<String, Object> entry : objectsMap.entrySet()) {Object obj = entry.getValue();Class<?> clazz = obj.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {try {field.setAccessible(true);if (field.getAnnotation(DI.class) != null) {Class<?>[] interfaces = field.getType().getInterfaces();String needWiredBeanName;Object autoWiredBean;// 优先使用注解DI的value注入if (StringUtils.hasText(needWiredBeanName = field.getAnnotation(DI.class).value())) {autoWiredBean = objectsMap.get(needWiredBeanName);if (autoWiredBean != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + " from " + "objectsMap, by value of annotation @DI");continue;} /*else {throw new NotExistsBean("Not exists the bean named <" + needWiredBeanName + "> to inject as property");}*/}// 没有父接口needWiredBeanName = lowercaseFirstLetter(field.getType().getSimpleName());if ((autoWiredBean = objectsMap.get(needWiredBeanName)) != null || (autoWiredBean = objectsTypeMap.get(field.getType())) != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + " , by value or type of property itself ");continue;}// 使用父接口的名字从bean容器中查找,注入if (interfaces.length > 0) {for (Class<?> interfaceClazz : interfaces) {String interfaceClazzName = interfaceClazz.getSimpleName();if (interfaceClazz.isAssignableFrom(field.getType())) {needWiredBeanName = lowercaseFirstLetter(interfaceClazzName);if ((autoWiredBean = objectsMap.get(needWiredBeanName)) != null || (autoWiredBean = objectsTypeMap.get(interfaceClazz)) != null) {field.set(obj, autoWiredBean);log.warn("<<< DI: Class " + clazz.getSimpleName() + " of field named " + field.getName() + " is injected with value of " + autoWiredBean + ", by value or type of super interface");}}}continue;}throw new InjectBeanException("There occurs an Exception while injecting property filed [" + field.getName() + "] of Class <" + clazz.getSimpleName() + "> , because bean factory doesn't exist the bean named " + needWiredBeanName);}} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}}private String lowercaseFirstLetter(String name) {return name.substring(0, 1).toLowerCase() + name.substring(1);}}

3. 定义DAO层和Service层及其实现

// 3.1 dao层
public interface UserDao {void addUser(User user);
}/** 
*  userDao实现
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {@Overridepublic void addUser(User user) {System.out.println();System.out.println("------------------>>>执行UserDaoImpl中的addUser方法开始<<<<------------------");System.out.println("\t\t\t\t\t\t名字\t\t\t\t年龄");System.out.println("\t\t\t\t\t\t"+user.getName()+"\t\t\t\t"+user.getAge());System.out.println("------------------>>>执行UserDaoImpl中的addUser方法完毕<<<<------------------");}
}// 3.2 service层public interface UserService {void add();
}// service实现
@Component("userService")
public class UserServiceImpl implements UserService {@DI("userDao")private UserDaoImpl userDao;@Overridepublic void add() {System.out.println("》》》 user service impl execute add method...");User user = new User("张三", 25);userDao.addUser(user);}
}

4. 定义异常信息类

4.1 InjectBeanException

public class InjectBeanException extends RuntimeException{public InjectBeanException() {super();}public InjectBeanException(String message) {super(message);}
}

4.2 NotExistsBean

public class NotExistsBean extends RuntimeException{public NotExistsBean() {}public NotExistsBean(String message) {super(message);}
}

4.3 NotSupportMoreSuperInterface

public class NotSupportMoreSuperInterface extends RuntimeException{public NotSupportMoreSuperInterface() {super();}public NotSupportMoreSuperInterface(String message) {super(message);}
}

5. 测试自定义bean容器(带依赖注入)

5.1 新建测试类TestUser

public class TestUser {public static void main(String[] args) {DefaultListableApplicationContext context = new DefaultListableApplicationContext("com.ypy.context");UserService userService = context.getBean("aaa", UserServiceImpl.class);UserDao userDao = context.getBean("userDaoImpl", UserDao.class);boolean existFlag = context.exists("aaa"/*, UserService.class*/);System.out.println("UserService exists ? " + existFlag);System.out.println("从Bean容器中获取aaa对象地址: " + userService);System.out.println("从Bean容器中获取userDao对象地址: " + userDao);userService.add();}
}

5.2 输出结果

一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsMap store bean(name,obj) ===> (userDao,com.ypy.context.dao.impl.UserDaoImpl@2b193f2d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsTypeMap store bean(class,obj) ===> (UserDao,com.ypy.context.dao.impl.UserDaoImpl@2b193f2d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsMap store bean(name,obj) ===> (userService,com.ypy.context.service.impl.UserServiceImpl@7a81197d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: >>> objectsTypeMap store bean(class,obj) ===> (UserService,com.ypy.context.service.impl.UserServiceImpl@7a81197d)
一月 18, 2024 4:01:20 上午 com.sun.org.slf4j.internal.Logger warn
警告: <<< DI: Class UserServiceImpl of field named userDao is injected with value of com.ypy.context.dao.impl.UserDaoImpl@2b193f2d from objectsMap, by value of annotation @DI
UserService exists ? false
从Bean容器中获取aaa对象地址: com.ypy.context.service.impl.UserServiceImpl@7a81197d
从Bean容器中获取userDao对象地址: com.ypy.context.dao.impl.UserDaoImpl@2b193f2d
》》》 user service impl execute add method...------------------>>>执行UserDaoImpl中的addUser方法开始<<<<------------------名字				年龄张三				25
------------------>>>执行UserDaoImpl中的addUser方法完毕<<<<------------------Process finished with exit code 0

相关文章:

【Spring之手写一个依赖注入容器】

Spring之手写一个依赖注入容器 1. 创建两个自定义注解1.1 Component注解1.2 DI注解 2. ApplicationContext接口与实现类2.1 ApplicationContext 接口2.2 实现类&#xff1a;DefaultListableApplicationContext 3. 定义DAO层和Service层及其实现4. 定义异常信息类4.1 InjectBean…...

kafka之java客户端实战

1. kafka的客户端 Kafka提供了两套客户端API&#xff0c;HighLevel API和LowLevel API。 HighLevel API封装了kafka的运行细节&#xff0c;使用起来比较简单&#xff0c;是企业开发过程中最常用的客户端API。 而LowLevel API则需要客户端自己管理Kafka的运行细节&#xff0c;Pa…...

图解渠道网关:不只是对接渠道的接口(一)

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;20&#xff09;篇。点击上方关注&#xff0c;深入了解支付系统的方方面面。 主要讲清楚什么是渠道&#xff0c;有哪些类型的渠道&#xff0c;什么是渠道网关&#xff0c;渠道网关在支付系统中定位、核心功能…...

【js版数据结构学习之队列】

队列 一、简要认识队列二、队列的封装三、队列的应用1.栈和队列的转换2.全排列3.任务调度4.缓存管理 一、简要认识队列 结构&#xff1a;一种特殊的线性表 入队&#xff1a;在队尾插入一个元素 出队&#xff1a;在队头删除一个元素 特点&#xff1a;先入先出 空队列&#xff1…...

iOS Xcode 升级Xcode15报错: SDK does not contain ‘libarclite‘

iOS Xcode 升级Xcode15报错: SDK does not contain libarclite 一、仔细查看报错代码: SDK does not contain libarclite at the path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/ lib/arc/libarclite_iphonesimulator.a; try in…...

python高级练习题库实验2(B)部分

文章目录 题目1代码实验结果题目2代码实验结果题目总结题目1 注册课程小游戏程序 研究下面的例子,并编写一个与这些例子完全相同的程序。使用for loop和break来解决问题。提示用户输入课程数量,是否选择,并且课程代码,最后还需显示已经完成的课程注册数量或者未完成的注册…...

vue项目编译非常慢,经常卡在某个百分点

1、注册插件 2、在项目根目录下的 babel.config.js 文件中加入下方配置 3、将import导入方式改为require导入方式&#xff0c;返回promise 4、如果动态加载组件import引入组件找不到组件&#xff08;Error: Cannot find module&#xff09; 使用 webpack 的 require.ensure() …...

开源知识库zyplayer-doc部署指南

1.前置条件 docker已经安装 mysql已经安装且数据库zyplayer-doc存在 服务器ip:192.168.168.99/ 数据库账户:root,密码:123456 2.拉取镜像 docker pull zyplayer/zyplayer-doc:latest 3.启动 docker run -d \--restart unless-stopped \--name zyplayer-doc \-p 8083:8083 …...

第90讲:MySQL数据库主从复制集群原理概念以及搭建流程

文章目录 1.MySQL主从复制集群的核心概念1.1.什么是主从复制集群1.2.主从复制集群中的专业术语1.3.主从复制集群工作原理1.4.主从复制中的小细节1.5.搭建主从复制集群的前提条件1.6.MySQL主从复制集群的架构信息 2.搭建MySQL多实例环境2.1.在mysql-1中搭建身为主库的MySQL实例2…...

PHP反序列化漏洞-魔术方法绕过

一、__wakeup()魔法函数绕过: 在PHP中,__wakeup()是一个魔术方法,用于在反序列化对象时自动调用。当反序列化字符串中的对象属性个数大于实际属性个数时,可以利用这个漏洞进行绕过。 触发条件: PHP版本为5.6.25或早期版本,或者PHP7版本小于7.0.10。反序列化字符串中的对…...

抖店和商品橱窗的区别?这两个千万别再搞混了!

我是电商珠珠 很多人都会将抖店和商品橱窗搞混&#xff0c;想开抖店的人开了商品橱窗&#xff0c;想开橱窗的人开通了抖店。 我做抖店三年了&#xff0c;这种情况屡见不鲜。 那么抖店和商品橱窗究竟有什么区别呢&#xff1f; 1、属性不同 商品橱窗是抖音所展现商品的一个功…...

个人总结钉钉7.5新品发布会

钉钉发布了 7.5 版本&#xff0c;最主要推出了围绕AI能力的各项升级&#xff0c;通过AI“超级助理”提升组织内部的沟通协作效率、管理决策智能化程度&#xff0c;以及相关的音视频、在线文档、Teambition功能的升级&#xff0c;以满足不同企业的多元化需求。截至发布会&#x…...

连接超时的问题

连接超时的问题 通用第三方工具连接超时 connect timeout 方案一&#xff1a; /etc/ssh/sshd_config node1上操作&#xff0c;图是错的 方案二&#xff1a; windows上Hosts文件域名解析有问题 比如&#xff1a; 192.168.xx.100 node1 192.168.xx.161 node1 两个都解析成node…...

python贪吃蛇游戏

为了实现这个游戏&#xff0c;需要用到Python的pygame模块&#xff0c;它是一个专门用于开发游戏的模块&#xff0c;提供了很多方便的功能&#xff0c;比如窗口、图形、音效、事件处理等。 用pygame来创建一个窗口&#xff0c;设置游戏的背景色&#xff0c;画出蛇和食物&#…...

【Spring】Spring AOP

文章目录 前言1. 什么是 AOP2. 什么是 Spring AOP3. Spring AOP 的使用引入 AOP 依赖编写 AOP 程序 4. Spring AOP 详解4.1 Spring AOP 的概念4.1.1 切点4.1.2 连接点4.1.3 通知4.1.4 切面 4.2 通知类型4.3 切点4.4 切面优先级 Order注解4.5 切点表达式4.5.1 execution 切点表达…...

软件开发架构

【 一 】软件开发架构图 【 1】ATM和选课系统 三层的开发架构 前段展示台 后端逻辑层 数据处理层 【二】软件开发架构的步骤流程 需求分析&#xff1a;在软件开发架构设计之前&#xff0c;需要对应用系统进行需求分析&#xff0c;明确用户需求、功能模块、业务流程等内容。…...

计图大模型推理库部署指南,CPU跑大模型,具有高性能、配置要求低、中文支持好、可移植等特点

Excerpt 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库,具有高性能、配置要求低、中文支持好、可移植等特点 计图大模型推理库 - 笔记本没有显卡也能跑大模型 本大模型推理库JittorLLMs有以下几个特点: 成本低:相比同类框架,本库…...

CSS||Emmet语法

1、简介 ​ Emmet语法的前身是Zen coding,它使用缩写,来提高html/css的编写速度, Vscode内部已经集成该语法。 ​ 快速生成HTML结构语法 ​ 快速生成CSS样式语法 2、快速生成HTML结构语法 生成标签 直接输入标签名 按tab键即可 比如 div 然后tab 键&#xff0c; 就可以生成 <…...

Android中的anr定位指导与建议

1.背景 8月份安卓出现了一次直播间卡死(ANR)问题&#xff0c;且由于排查难度较大&#xff0c;持续了较长时间。本文针对如何快速定位安卓端出现ANR问题进行总结和探讨. 这里大致补充一下当时的情况,当时看到情景的是从某一个特定的场景下进入直播间后整个直播间界面立刻就卡住…...

YOLOV7剪枝流程

YOLOV7剪枝流程 1、训练 1&#xff09;划分数据集进行训练前的准备&#xff0c;按正常的划分流程即可 2&#xff09;修改train.py文件 第一次处在参数列表里添加剪枝的参数&#xff0c;正常训练时设置为False&#xff0c;剪枝后微调时设置为True parser.add_argument(--pr…...

双屏天花板用法!YogaBook 9i 多屏操作全演示

YogaBook 9i 作为双屏笔记本里的标志性机型&#xff0c;凭借两块高素质触控屏带来了完全不同于传统电脑的使用体验&#xff0c;但不少用户拿到手只当普通笔记本使用&#xff0c;没能发挥双屏协同的真正效率&#xff0c;多屏联动、分屏操作、跨屏交互这些核心亮点都被白白浪费。…...

iText7中文渲染完全指南:从乱码到完美显示的技术突破

iText7中文渲染完全指南&#xff1a;从乱码到完美显示的技术突破 【免费下载链接】itext7-chinese-font 项目地址: https://gitcode.com/gh_mirrors/it/itext7-chinese-font 在数字化文档处理领域&#xff0c;PDF格式以其跨平台一致性成为信息传递的首选。然而&#xf…...

Stable Diffusion VAE重构图像效果不理想?可能是你忘了调整这个关键参数

Stable Diffusion VAE图像重构效果优化指南&#xff1a;关键参数解析与实战调整 当你第一次使用Stable Diffusion的VAE&#xff08;Variational Autoencoder&#xff09;进行图像重构时&#xff0c;可能会遇到这样的困惑&#xff1a;明明按照教程一步步操作&#xff0c;为什么输…...

Qwen3.5-4B-Claude推理模型在算法面试辅导中的实战:分步推导+代码示例生成

Qwen3.5-4B-Claude推理模型在算法面试辅导中的实战&#xff1a;分步推导代码示例生成 1. 模型介绍与核心能力 Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF是一个专门针对推理任务优化的AI模型&#xff0c;它基于Qwen3.5-4B架构&#xff0c;通过蒸馏训练强化了结构化…...

XU9232A可穿戴设备 电池供电设备 便携式医疗设备

这是一款高度集成的升压转换器&#xff0c;为输出高电压和高效率的应用方案而设计。输入电源可以从一个锂电池或二节串联的碱性电池&#xff0c;而升压到最高18V&#xff1b;工作频率为 1.2MHz&#xff08;典型值&#xff09;。内置典型4A开关晶体管&#xff0c;其组成 DC/DC 升…...

AR.js实战指南:如何在Web浏览器中构建高效增强现实应用

AR.js实战指南&#xff1a;如何在Web浏览器中构建高效增强现实应用 【免费下载链接】AR.js Image tracking, Location Based AR, Marker tracking. All on the Web. 项目地址: https://gitcode.com/gh_mirrors/arj/AR.js 在移动设备普及的今天&#xff0c;增强现实&…...

OpenClaw版本升级:nanobot镜像迁移全记录

OpenClaw版本升级&#xff1a;nanobot镜像迁移全记录 1. 升级背景与准备工作 去年我在本地部署了基于OpenClaw v1.2的nanobot镜像&#xff0c;这套系统一直稳定运行着我的自动化办公流程。直到上个月收到社区通知&#xff0c;新版本v2.1重构了核心架构&#xff0c;特别是技能…...

wan2.1-vae提示词评估体系:构建BLEU-Style指标量化中文提示词有效性

wan2.1-vae提示词评估体系&#xff1a;构建BLEU-Style指标量化中文提示词有效性 1. 为什么需要评估提示词质量 在AI图像生成领域&#xff0c;提示词的质量直接影响最终生成效果。好的提示词能准确表达创作意图&#xff0c;而模糊或不当的提示词可能导致生成结果与预期不符。特…...

ESP32开发实战:5分钟搞定MicroPython调用C库驱动LED(附完整代码)

ESP32混合编程实战&#xff1a;用MicroPython调用C库实现高性能LED控制 在物联网设备开发中&#xff0c;ESP32凭借其出色的性价比和丰富的功能接口成为硬件开发者的首选。而MicroPython作为嵌入式领域的Python实现&#xff0c;以其简洁的语法和快速的开发周期赢得了大量开发者的…...

当心“Pin-to-Pin兼容“陷阱:ICM-42688国产替代芯片深度拆解与避坑指南

两句话总结&#xff1a;近期TDK ICM-42688-P价格暴涨至百元且一芯难求&#xff0c;立创商城上出现了华轩阳、Tokmas等"国产替代"。本文通过详细对比三家datasheet数据手册&#xff0c;揭示所谓"兼容"背后的软件陷阱与性能差异。结论可能出乎你意料&#xf…...