Sping源码(九)—— Bean的初始化(非懒加载)— FactoryBean

FactoryBean

先来介绍一下FactoryBean是什么。以及BeanFactory和FactoryBean的区别。
在这里插入图片描述
举个栗子:
MyFactoryBean.class

public class MyFactoryBean implements FactoryBean<User> {
	@Override
	public User getObject() throws Exception {

		return new User("zhangsan");;
	}

	@Override
	public Class<?> getObjectType() {
		return User.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}
}

User.class

public class User {

	private String name;

	public User() {}

	public User(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

新建一个factoryBean.xml,将我们创建的MyFactoryBean交由Spring进行管理。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans 
	   http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="org.springframework.custom.MyFactoryBean" id="userFactoryBean">
	</bean>
</beans>

main
准备工作都已经做完,接下来我们看看在Spring中如何创建的User对象。

public static void main(String[] args) {
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("factoryBean.xml");
		User u = ac.getBean("userFactoryBean",User.class);
		System.out.println(u.getName());
	}

加载

我们先来看这样几个问题,并带着问题这么几个问题来阅读源码。

  1. 上面xml中< bean >标签的class属性值是MyFactoryBean的全限定名,为什么main方法中通过getBean()可以获取到User对象。
  2. main方法走完,当使用FactoryBean创建对象的时候,创建了几个?
  3. 创建的对象是否由Spring进行管理。

我们先回到preInstantiateSingletons方法,看看mergeBeanDefinition后都干了什么。

preInstantiateSingletons
merge后,进行了类型判断、如果当前BeanDefinition不是抽象类、并且是单例、并且不是懒加载,则会进入到下一个 if 判断中。
而我们当前beanDefinition为上文中创建的 MyFactoryBean, 所以理所当然的属于FactoryBean。

而我们想要获取到User对象,必然要先得到 MyFactoryBean 对象。
所以经过 getBean -> doGetBean -> createBean -> doCreateBean 的一系列操作后,返回的Object bean 是 MyFactoryBean。

public void preInstantiateSingletons() throws BeansException {
		//获取所有beanDefinitionName
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			//合并父类beanDefinition
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			//如果不是抽象类,并且是单例,并且不是懒加载
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				//是否实现了FactoryBean接口
				if (isFactoryBean(beanName)) {
					//根据 &+beanName获取具体实例对象
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
					}
				// 省略部分代码....
				}
			}
		}
	}

可以看到我们当前的beanName是创建的userFactoryBean,而BeanFactory中的singletonObject缓存中也已经有以userFactoryBean为key的缓存,此时通过该key 可以获取到 MyFactoryBean实例。
在这里插入图片描述

但是整个流程走完,会发现只有MyBeanFactory对象的创建,并没有我们的User对象,而我们上面的demo代码中可以清晰的看到,想要获取User对象,要调用 getObject()方法,上面的流程中并没有。接下来让我们回到 main 方法的入口。

getObject

这次我们从main方法中的 ac.getBean入口进入,看看都干了什么。
依然是getBean -> doGetBean -> createBean -> doCreateBean的流程。

public static void main(String[] args) {
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("factoryBean.xml");
		User u = ac.getBean("userFactoryBean",User.class);
		System.out.println(u.getName());
}

getBean -> doGetBean

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
		return doGetBean(name, requiredType, null, false);
}

doGetBean
截取部分源码。 当前的 mbd 依然是我们自定义的MyFactoryBean

			// Create bean instance.
				//bean的范围是 SINGLETON 或者 DEFAULT
				if (mbd.isSingleton()) {
					//从缓存中获取实例bean
					sharedInstance = getSingleton(beanName, () -> {
						try {
							//创建bean
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				// 如果 bean的范围是 PROTOTYPE
				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					//如果是 PROTOTYPE,则创建一个新实例
					Object prototypeInstance = null;
					try {
						//使用 ThreadLocal 作为缓存
						beforePrototypeCreation(beanName);
						//创建实例
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						//创建完实例后,将beanName从ThreadLocal中移除
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					//如果scopeName 为 null 则抛出异常。
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							//使用 ThreadLocal 作为缓存
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								//创建完实例后,将beanName从ThreadLocal中移除
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
				}

在这里插入图片描述

并且代码中无论走哪个 if else 的分支,都会通过 createBean() —》 doCreateBean()方法进行实例的创建,但这时创建出来的实例也依然是 myFactoryBean
在这里插入图片描述
此时我们想要的User.class还是没创建,但是别急,我们在接着往下看看 createBean() 过后的 getObjectForBeanInstance

getObjectForBeanInstance
方法主要是做一些判断并设置标志位mbd.isFactoryBean = true;,方法的最后会调用getObjectFromFactoryBean

protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

		// Don't let calling code try to dereference the factory if the bean isn't a factory.
		// 如果Bean不是工厂,不要让调用代码尝试取消对工厂的引用
		// 如果name为FactoryBean的解引用.name是以'&'开头,就是FactoryBean的解引用
		if (BeanFactoryUtils.isFactoryDereference(name)) {
			//如果beanInstance是NullBean,则直接返回beanInstance
			if (beanInstance instanceof NullBean) {
				return beanInstance;
			}
			//如果beanInstance不是FactoryBean,则抛出异常
			if (!(beanInstance instanceof FactoryBean)) {
				throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
			}
			//如果beanInstance是FactoryBean,则设置mbd.isFactoryBean为true
			if (mbd != null) {
				mbd.isFactoryBean = true;
			}
			//返回beanInstance
			return beanInstance;
		}

		// Now we have the bean instance, which may be a normal bean or a FactoryBean.
		// If it's a FactoryBean, we use it to create a bean instance, unless the
		// caller actually wants a reference to the factory.
		// 如果beanInstance是FactoryBean,则使用它来创建一个bean实例,除非调用者实际上想要一个对工厂的引用。
		//如果不是,则返回beanInstance
		if (!(beanInstance instanceof FactoryBean)) {
			return beanInstance;
		}

		Object object = null;
		//如果mbd不为空,则设置mbd.isFactoryBean为true
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		else {
			//如果mbd为空,则从缓存中获取beanInstance
			object = getCachedObjectForFactoryBean(beanName);
		}
		if (object == null) {
			// Return bean instance from factory.
			//将beanInstance强转成FactoryBean
			FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
			// Caches object obtained from FactoryBean if it is a singleton.
			//缓存从FactoryBean获得的对象,如果它是单例的。
			//如果当前BeanDefinition为null && 当前beanFactory的beanDefinitionMap中包含当前beanName的BeanDefinition
			if (mbd == null && containsBeanDefinition(beanName)) {
				//获取beanName合并后的本地RootBeanDefintiond对象
				mbd = getMergedLocalBeanDefinition(beanName);
			}
			boolean synthetic = (mbd != null && mbd.isSynthetic());
			//从FactoryBean中获取bean
			object = getObjectFromFactoryBean(factory, beanName, !synthetic);
		}
		return object;
	}

getObjectFromFactoryBean
从FacrotyBean中获取Bean实例。
factoryBeanObjectCache缓存中尝试获取,获取不到则调用doGetObjectFromFactoryBean方法创建实例Bean,并放到factoryBeanObjectCache缓存中。

需要注意的是,我是要通过自定义的MyFactoryBean创建User.class,所以此时的beanName 依然是 xml中配置的 bean标签的id userFactoryBean。factory也是创建好的实例 MyFactoryBean

	protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
		//是否是单例 && 缓存中是否包含该对象
		if (factory.isSingleton() && containsSingleton(beanName)) {
			synchronized (getSingletonMutex()) {
				//从factoryBeanObjectCache缓存中获取
				Object object = this.factoryBeanObjectCache.get(beanName);
				if (object == null) {
					//调用FactoryBean的getObject方法获取对象
					object = doGetObjectFromFactoryBean(factory, beanName);
					// Only post-process and store if not put there already during getObject() call above
					// (e.g. because of circular reference processing triggered by custom getBean calls)
					// 仅在上面的getObject()调用期间进行后处理和存储(如果尚未放置)
					// (例如,由于自定义getBean调用触发的循环引用处理)
					// 重新从factoryBeanObjectCache中获取beanName对应bean对象
					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
					//如果alreadyThere != null
					//让object 引用 alreadyThere
					if (alreadyThere != null) {
						object = alreadyThere;
					}
					else {
						//如果要进行后处理
						if (shouldPostProcess) {
							//如果是单例对象正在创建中
							if (isSingletonCurrentlyInCreation(beanName)) {
								// Temporarily return non-post-processed object, not storing it yet..
								// 暂时返回非后处理对象,不存储它,直接返回object
								return object;
							}
							// 创建单例之前的回调
							beforeSingletonCreation(beanName);
							try {
								// 对从FactoryBean获得的给定对象进行后处理.
								object = postProcessObjectFromFactoryBean(object, beanName);
							}
							catch (Throwable ex) {
								throw new BeanCreationException(beanName,
										"Post-processing of FactoryBean's singleton object failed", ex);
							}
							finally {
								// 建单例之后的回调
								afterSingletonCreation(beanName);
							}
						}
						// 如果singletonObjects缓存中存在该beanName
						if (containsSingleton(beanName)) {
							//放入factoryBeanObjectCache缓存中
							this.factoryBeanObjectCache.put(beanName, object);
						}
					}
				}
				return object;
			}
		}
		else {
			//当前factory不是单例 || singletonObjects缓存中不包含该beanName
			//调用FactoryBean的getObject方法获取对象
			Object object = doGetObjectFromFactoryBean(factory, beanName);
			//如果要进行后处理
			if (shouldPostProcess) {
				try {
					// 对从FactoryBean获得的给定对象进行后处理.
					object = postProcessObjectFromFactoryBean(object, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
				}
			}
			return object;
		}
	}

在这里插入图片描述

doGetObjectFromFactoryBean

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
		Object object;
		try {
			//如果有安全管理器 
			if (System.getSecurityManager() != null) {
				// 忽略...
			}
			else {
			    //通过调用getObject()方法获取Bean实例
				object = factory.getObject();
			}
		}

		// Do not accept a null value for a FactoryBean that's not fully
		// initialized yet: Many FactoryBeans just return null then.
		//不接受不完全的FactoryBean的空值,
		if (object == null) {
			// 如果 beanName当前正在创建(在整个工厂内)
			if (isSingletonCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(
						beanName, "FactoryBean which is currently in creation returned null from getObject");
			}
			// 让object引用一个新的NullBean实例
			object = new NullBean();
		}
		return object;
	}

可以看到,此时方法返回的Object对象就是我们想要的User对象。
在这里插入图片描述
顺着getObjectFromFactoryBean方法继续向下走,直到return object时我们再通过debug查看当前容器(BeanFactory)中的singletonObjects缓存,发现里面依然没有我们创建的 User 对象,不过factoryBeanObjectCache缓存中却多了一个 key = userFactoryBean的值。
但是!!! 虽然两个缓存中的key相同,但指向的却是两个对象 singletonObjects 中是 MyFactoryBeanfactoryBeanObjectCache中指向的是User对象
在这里插入图片描述

总结

回到文章开始我们提出的三个问题,随着对源码的了解,问题的答案也渐渐清晰:

  1. 上面xml中< bean >标签的class属性值是MyFactoryBean的全限定名,为什么main方法中通过getBean()可以获取到User对象。
    1.1 首先创建MyFactoryBean的实例,并通过getObject()创建User对象并返回。
  2. main方法走完,当使用FactoryBean创建对象的时候,创建了几个?
    2.1 2个对象,1个MyFactoryBean对象 1个User对象
  3. 创建的对象是否由Spring进行管理。
    3.1 会由Spring进行管理么? 我们再次debug查看一下。

main
我们稍微修改一下main方法,看此时u1 u2是否相等。

public static void main(String[] args) {
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("factoryBean.xml");
		User u1 = ac.getBean("userFactoryBean",User.class);
		User u2 = ac.getBean("userFactoryBean",User.class);
		System.out.println(u1 == u2);
	}

源码中有提到,当我们第一次创建User对象后,会放入factoryBeanObjectCache缓存中,所以当u2进行加载时,会直接从factoryBeanObjectCache缓存获取。

getCachedObjectForFactoryBean

protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
		//省略部分源码。。。
		
		Object object = null;
		//如果mbd不为空,则设置mbd.isFactoryBean为true
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		else {
			//如果mbd为空,则从缓存中获取beanInstance
			object = getCachedObjectForFactoryBean(beanName);
		}
		// 省略部分源码
		return object;
	}
protected Object getCachedObjectForFactoryBean(String beanName) {
		return this.factoryBeanObjectCache.get(beanName);
	}

当u2进来时,factoryBeanObjectCache缓存不为null,从缓存中取值后直接 return。
在这里插入图片描述
还记得MyFactory类中的isSingleton()方法么?当时我们是默认给的true,现在我们改成false再看看u1 u2还是否相等。

public class MyFactoryBean implements FactoryBean<User> {
	@Override
	public User getObject() throws Exception {

		return new User("zhangsan");
	}

	@Override
	public Class<?> getObjectType() {
		return User.class;
	}

	@Override
	public boolean isSingleton() {
		return false;
	}
}

再次debug会发现,factoryBeanObjectCache缓存为null,当u2调用getBean()方法时,还是会再走一遍之前的创建流程。

所以对于问题3的答案:
如果实现的isSingleton() 方法返回是true,则创建的对象会放入,factoryBeanObjectCache缓存中交由Spring进行管理,如果isSingleton() 方法返回的是false,则每次都会创建新对象。不会交给Spring进行管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/666607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CAPL如何发送一条UDP报文

UDP作为传输层协议,本身并不具有可靠性传输特点,所以不需要建立连接通道,可以直接发送数据。当然,前提是需要知道对方的通信端点,也就是IP地址和端口号。 端口号是传输层协议中最显著的特征,传输层根据它来确定上层绑定的应用程序,以达到把数据交给上层应用处理的目的。…

五种主流数据库:常用数据类型

在设计数据库的表结构时&#xff0c;我们需要明确表中包含哪些字段以及字段的数据类型。字段的数据类型定义了该字段能够存储的数据种类以及支持的操作。 本文将会介绍五种主流数据库中常用的数据类型以及如何选择合适的数据类型&#xff0c;包括 MySQL、Oracle、SQL Server、…

基于Springboot + vue实现的文化民俗网站

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

uni-app的网络请求库封装及使用(同时支持微信小程序)

其实uni-app中内置的uni.request()已经很强大了&#xff0c;简单且好用。为了让其更好用&#xff0c;同时支持拦截器&#xff0c;支持Promise 写法&#xff0c;特对其进行封装。同时支持H5和小程序环境&#xff0c;更好用啦。文中给出使用示例&#xff0c;可以看到使用变得如此…

安卓Zygote进程详解

目录 一、概述二、Zygote如何被启动的&#xff1f;2.1 init.zygote64_32.rc2.2 Zygote进程在什么时候会被重启2.3 Zygote 启动后做了什么2.4 Zygote启动相关主要函数 三、Zygote进程启动源码分析3.1 Nativate-C世界的Zygote启动要代码调用流程3.1.1 [app_main.cpp] main()3.1.2…

11- Redis 中的 SDS 数据结构

字符串在 Redis 中是很常用的&#xff0c;键值对中的键是字符串类型&#xff0c;值有时也是字符串类型。 Redis 是用 C 语言实现的&#xff0c;但是它没有直接使用 C 语言的 char* 字符数组来实现字符串&#xff0c;而是自己封装了一个名为简单动态字符串&#xff08;simple d…

13.优化界面化的游戏辅助

12.使用mfc实现游戏辅助的界面 在它的代码上进行修改 12.使用mfc实现游戏辅助的界面它的代码是频繁读写游戏的内存&#xff0c;这样不是很好&#xff0c;下面的代码是在它的基础上进行了封装&#xff0c;控制无敌的逻辑在我们申请的内存中实现&#xff08;也就是在一个全局中实…

gcc 内建函数示例 __builtin_return_address

1,理论未动&#xff0c;示例先行 hello_gcc_callstack.c #include <stdio.h>void do_backtrace() {void *pc0 __builtin_return_address(0);void *pc1 __builtin_return_address(1);void *pc2 __builtin_return_address(2);void *pc3 __builtin_return_address(3);…

oracle中的INTERVAL函数学习总结

Oracle 从9i数据库开始引入了一种新特性&#xff0c;可以用来存储时间间隔&#xff0c;出现了INTERVAL 函数。这个函数的表达式比较多&#xff0c;初学比较费劲不好掌握&#xff0c;经过以几个小时的查阅资料和实验&#xff0c;总结如下&#xff1a; interval year t…

使用Redis常遇到的问题

文章目录 概述缓存雪崩、穿透、击穿大key问题热Key问题缓存和数据库双写一致性问题缓存并发竞争Redis线上阻塞要如何排查Redis 常见的性能问题都有哪些Redis 如何做内存优化Redis数据倾斜 概述 在使用Redis时&#xff0c;有几个常见的问题可能会出现&#xff0c;包括但不限于以…

2022年全国职业院校技能大赛高职组“信息安全管理与评估”赛项第三阶段任务书

第三阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛-第三阶段试题。根据信息安全管理与评估项目技术文件要求&#xff0c;第三阶段为夺旗挑战CTF&#xff08;网络安全渗透&#xff09;。 本次比赛时间为180分钟。 介绍 夺旗挑战赛&#xff08;CTF&#xff09;的目标…

21 厂商考证介绍(华为 华三 锐键 深信服)+AI 解析

一 认识考证体系 二 明确考证的大致方向 锐键 职业资格证书等级介绍 职业资格证书是由国家职业资格鉴定机构或相关行业主管部门颁发的&#xff0c;用于证明一个人在特定职业领域具备一定技能和知识水平的证明文件。职业资格证书的等级分为初级、中级、高级、技师、高级技师、…

算法每日一题(python,2024.05.29) day.11

题目来源&#xff08;力扣. - 力扣&#xff08;LeetCode&#xff09;&#xff0c;简单&#xff09; 解题思路&#xff1a; 法一&#xff1a;切片函数法 直接用python中的切片函数直接解决 法二&#xff1a;交换法 从俩头开始交换字符串的数字&#xff0c;若为奇数&#xff…

CSRF跨站请求伪造漏洞

CSRF跨站请求伪造漏洞 1.CSRF漏洞概述2.防御CSRF攻击3.CSRF防御绕过CSRF令牌未绑定到用户会话自定义标头令牌绕过绕过Referer检查关键词绕过 4.利用示例使用HTML标签进行GET表单 GET 请求表单POST请求通过 iframe 发送表单 POST 请求Ajax POST 请求 5.CSRF BP 验证方法6.CSRF测…

LabVIEW老程序功能升级:重写还是改进?

概述&#xff1a;面对LabVIEW老程序的功能升级&#xff0c;开发者常常面临重写与改进之间的选择。本文从多个角度分析两种方法的利弊&#xff0c;并提供评估方法和解决思路。 重写&#xff08;重新开发&#xff09;的优势和劣势&#xff1a; 优势&#xff1a; 代码清晰度高&a…

【R语言基础】如何更新R版本

文章目录 概要流程细节具体步骤 概要 提示&#xff1a;由于软件包的更新&#xff0c;所以需要更新R至新版本 流程细节 查看当前R版本 R.version下载更新包&#xff1a;installr install.packages("installr")library(installr)跟着向导一步步执行安装 具体步骤 …

使用Spring Boot自定义注解 + AOP实现基于IP的接口限流和黑白名单

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

技术分享 | SpringBoot 流式输出时,正常输出后为何突然报错?

项目背景 一个 SpringBoot 项目同时使用了 Tomcat 的过滤器和 Spring 的拦截器&#xff0c;一些线程变量在过滤器中初始化并在拦截器中使用。该项目需要调用大语言模型进行流式输出。项目中&#xff0c;笔者使用 SpringBoot 的 ResponseEntity<StreamingResponseBody> 将…

java实现地形dem产汇流流场数据提取解析

一、基础概念 在GIS和气象学、海洋学、大气科学、水文学等领域&#xff0c;"提取流场"通常指的是从数据集中识别和分析流体&#xff08;如水流、风场、洋流、大气流&#xff09;的运动模式和流向的过程。这个过程涉及数据处理、可视化和分析技术&#xff0c;下面是提…

LDR6500一拖二快充线方案

随着科技的飞速发展&#xff0c;我们的电子设备日益增多&#xff0c;从智能手机到平板电脑&#xff0c;再到各种可穿戴设备&#xff0c;它们已成为我们日常生活不可或缺的一部分。然而&#xff0c;随之而来的充电问题也日益凸显。为了解决这一难题&#xff0c;Type-C接口一拖二…