dubbo之服务本地暴露
寫在前面
源碼 。
服務(wù)提供者是標(biāo)記了@Service注解的類,想要被服務(wù)消費(fèi)者使用,必須將服務(wù)暴露出去,即讓服務(wù)消費(fèi)者拿到封裝服務(wù)信息的com.alibaba.dubbo.common.URL對(duì)象字符串,當(dāng)前有三種服務(wù)暴露方式:
遠(yuǎn)程暴露:即將服務(wù)信息注冊(cè)到遠(yuǎn)端注冊(cè)中心,如配置<dubbo:service scope="remote" />。 本地暴露:JVM內(nèi)部調(diào)用,因?yàn)樾畔⒁呀?jīng)在內(nèi)存中,通過(guò)內(nèi)存可以直接獲取調(diào)用信息,因此叫做本地暴露,如配置<dubbo:service scope="local">。 不暴露:不暴露服務(wù),可以忽略這種方式,如配置<dubbo:service scope="none">。本文來(lái)分享的是本地暴露,相關(guān)的源碼在模塊dubbo-rpc-injmv中,如下圖:
在dubbo之服務(wù)提供者配置 一文中,我們其實(shí)分析了部分服務(wù)暴露的內(nèi)容,大家可以看下,本文為了承接,會(huì)有部分內(nèi)容的重疊,就從方法com.alibaba.dubbo.config.ServiceConfig.doExportUrls來(lái)開始分析。
1:doExportUrls
源碼如下:
class FakeCls {private void doExportUrls() {// 2022-01-21 18:25:43List<URL> registryURLs = loadRegistries(true);// 循環(huán)所有的協(xié)議暴露服務(wù)到所有的注冊(cè)中心地址// 協(xié)議:protocols,即<dubbo:protocol>設(shè)置// 服務(wù):通過(guò)<dubbo:service>設(shè)置// 注冊(cè)中心地址:registryURLs,通過(guò)<dubbo:registry>指定for (ProtocolConfig protocolConfig : protocols) {// 2022-01-21 18:34:22doExportUrlsFor1Protocol(protocolConfig, registryURLs);}} }2022-01-21 18:25:43處獲取配置的所有注冊(cè)中心地址,具體參考1.1:loadRegistries。2022-01-21 18:34:22處是將服務(wù)按照指定的協(xié)議注冊(cè)到注冊(cè)中心,具體參考1.2:doExportUrlsFor1Protocol。
1.1:loadRegistries
本文講解的時(shí)本地服務(wù)暴漏,不會(huì)使用到這里的信息,但是為了完整性,放在這里,對(duì)這部分感興趣的朋友可以參考dubbo之服務(wù)遠(yuǎn)程暴露 文章分析。
1.2:doExportUrlsFor1Protocol
將服務(wù)按照指定的協(xié)議注冊(cè)到注冊(cè)中心,分為遠(yuǎn)程暴漏和本地暴漏,其中本地暴漏不會(huì)注冊(cè)服務(wù)到注冊(cè)中心,因?yàn)槭峭粋€(gè)JVM,信息可以直接從JVM中獲取到,因?yàn)楸疚闹攸c(diǎn)分析的是本地服務(wù)暴漏,所以關(guān)于遠(yuǎn)程暴漏的相關(guān)源碼會(huì)選擇性忽略,關(guān)于這部分的分析,可以才參考dubbo之服務(wù)遠(yuǎn)程暴露 文章分析。源碼如下:
class FakeCls {private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {// 協(xié)議的名稱,如<dubbo:protocol name="dubbo" port="20826">,這里就是dubbo// 協(xié)議:就是暴漏自己的方法String name = protocolConfig.getName();// 沒(méi)有則默認(rèn)使用dubboif (name == null || name.length() == 0) {name = "dubbo";}//*** 省略構(gòu)建URL相關(guān)代碼 ***//// 獲取scope,如果是本地暴漏的話配置如:<dubbo:service interface="dongshi.daddy.service.scopelocal.ScopeLocalService" ref="scopeLocalService" scope="local"/>// 在文章開頭也提到了可配置為remote,代表遠(yuǎn)程暴漏,none代表不爆露String scope = url.getParameter(Constants.SCOPE_KEY);// 如果是配置scope="none",不進(jìn)行任何操作,此時(shí)不進(jìn)行暴漏,即不對(duì)外使用if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {// 如果是scope不是remote則使用本地服務(wù)暴漏if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {// 2022-01-22 12:25:51exportLocal(url);}// 如果是scope不是local則使用遠(yuǎn)程服務(wù)暴漏if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {// *** 省略遠(yuǎn)程服務(wù)暴漏邏輯 *** //}}// 添加暴漏服務(wù)urlthis.urls.add(url);} }2022-01-22 12:25:51處是本地服務(wù)暴漏,具體參考1.3:exportLocal。
1.3:exportLocal
源碼如下:
class FakeCls {private void exportLocal(URL url) {// url.getProtocol():一般是dubbo // Constants.LOCAL_PROTOCOL:injvm// 為什么做這個(gè)判斷???if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {// 構(gòu)建local的URL,如// injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?accesslog=true&anyhost=true&application=dongshidaddy-provider&bean.name=dongshi.daddy.service.scopelocal.ScopeLocalService&bind.ip=192.168.2.107&bind.port=20826&dubbo=2.0.2&generic=false&interface=dongshi.daddy.service.scopelocal.ScopeLocalService&methods=sayHi&owner=dongshidaddy&pid=6324&scope=local&side=provider×tamp=1642823993714URL local = URL.valueOf(url.toFullString()).setProtocol(Constants.LOCAL_PROTOCOL).setHost(LOCALHOST).setPort(0);// public static final String SERVICE_IMPL_CLASS = "service.classimpl";// url.getServiceKey():dongshi.daddy.service.scopelocal.ScopeLocalService// getServiceClass(ref:class dongshi.daddy.service.scopelocal.ScopeLocalServiceImpl// 將服務(wù)類的信息存儲(chǔ)到StaticContext中StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));// 2022-01-22 17:21:22Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));exporters.add(exporter);logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");}} }該小節(jié)以下部分稍微有點(diǎn)繞,大家吃耐心,不懂的話,多看幾遍!!!
2022-01-22 17:21:22處protocol定義為private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();,可以看到是獲取自適應(yīng)擴(kuò)展類 ,其中從Protocol接口也可以看出來(lái),源碼如下:
/*** Protocol. (API/SPI, Singleton, ThreadSafe)*/ @SPI("dubbo") public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy(); }可以看到export方法是標(biāo)注了@Adaptive 注解的,此處protocol
是Protocol#Adaptive,這個(gè)很好理解,因?yàn)楂@取就是動(dòng)態(tài)生成的自適應(yīng)子類,通過(guò)其調(diào)用真正的擴(kuò)展實(shí)現(xiàn)類,那么想要知道調(diào)用的到底是誰(shuí)就需要知道生成的代碼到是什么樣子的,我們可以通過(guò)如下的步驟來(lái)獲取其內(nèi)容:
如下是我獲取的內(nèi)容:
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {if (arg1 == null) throw new IllegalArgumentException("url == null");com.alibaba.dubbo.common.URL url = arg1;String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);} }我們重點(diǎn)關(guān)注其中的export方法,可以看到是調(diào)用url.getProtocol()作為目標(biāo)擴(kuò)展類的名稱,那么是什么值呢?我們的url為injvm://127.0.0.1/...可以看到協(xié)議是injvm,那么對(duì)應(yīng)的擴(kuò)展類是誰(shuí)呢,可以從文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中找到答案,其中key為injvm的的配置項(xiàng)內(nèi)容是injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,因此最終調(diào)用的擴(kuò)展類類是com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,但是真的是這樣嗎?我們來(lái)debug看一下,如下圖:
從圖中可以看出,還分別調(diào)用了QosProtocolWrapper,ProtocolListenerWrapper,ProtocolFilterWrapper,這是Protocol的Wrapper類,我們從META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中可以看出來(lái),如下圖:
關(guān)于Wrapper詳細(xì)可以參考dubbo之SPI Wrapper分析 。
最終調(diào)用過(guò)程為Protocol$Adaptive->QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->InjvmProtocol。具體的我們?cè)?:Protocol分析。
2:Protocol
源碼如下:
@SPI("dubbo") public interface Protocol {// 獲取當(dāng)前協(xié)議在沒(méi)有配置端口時(shí)的默認(rèn)端口號(hào)int getDefaultPort();// 暴漏service供遠(yuǎn)程調(diào)用// 1:協(xié)議對(duì)象需要在收到一個(gè)請(qǐng)求后記錄遠(yuǎn)程源的的地址,通過(guò)API RpcContext.getContext().setRemoteAddress()// 2:該方法必須具備冪等性(idempotent [a?'demp?t?nt]),即通過(guò)該方法調(diào)用一次或者是多次來(lái)暴漏一個(gè)URL沒(méi)有任何差別// 3:Invoker實(shí)例需要被框架傳入進(jìn)來(lái),protoco擴(kuò)展類需要用到,如自適應(yīng)時(shí)使用// 返回值:Exporter<T>,引用的是被暴漏的service,之后如果是需要取消暴漏的話需要用到@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy(); }2.1:Protocol$Adaptive
如何獲取該類信息可以參考1.3:exportLocal。
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {public void destroy() {// 因?yàn)闆](méi)有標(biāo)注@Adaptive注解,所以直接拋出java.lang.UnsupportedOperationExceptionthrow new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}// 因?yàn)闆](méi)有標(biāo)注@Adaptive注解,所以直接拋出java.lang.UnsupportedOperationExceptionpublic int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");}public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {if (arg1 == null) throw new IllegalArgumentException("url == null");com.alibaba.dubbo.common.URL url = arg1;String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null)throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());if (extName == null)throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);} }2.2:ProtocolListenerWrapper
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {// 以下遠(yuǎn)端暴漏才會(huì)執(zhí)行,這里可以忽略,因?yàn)閡rl是injvm://打頭if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);}// 2022-01-23 19:29:28return new ListenerExporterWrapper<T>(protocol.export(invoker),Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));} }2022-01-23 19:29:28處protocol.export(invoker)繼續(xù)調(diào)用裝飾的protocol類,這里調(diào)用的就是ProtocolFilterWrapper,關(guān)于該類參考2.3:ProtocolFilterWrapper。Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))處是使用keyexporter.listener,從url獲取值從而獲取要激活的ExporterListener擴(kuò)展類。ListenerExporterWrapper構(gòu)造函數(shù)源碼如下:
class FakeCls {public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {} }該監(jiān)聽器的作用是用來(lái)監(jiān)聽Exporter暴漏完畢和取消暴漏完畢。
2.3:ProtocolFilterWrapper
主要用于給Invoker增加Filter過(guò)濾器鏈,在調(diào)用真正的服務(wù)方法之前會(huì)調(diào)用過(guò)濾器Filter的邏輯,具體參考2.3.1:export。
2.3.1:export
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {// 此處要求協(xié)議是registry://,即遠(yuǎn)程暴露,這里是injvm://,所以可以忽略if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {return protocol.export(invoker);}// 2022-01-24 16:02:53// 這里的protocol就是InJvmProtocolreturn protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));}}2022-01-24 16:02:53處buildInvokerChain添加Filter鏈,具體參考2.3.2:buildInvokerChain。
2.3.2:buildInvokerChain
源碼如下:
class FakeCls {private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {Invoker<T> last = invoker;// 獲取激活的Filter擴(kuò)展類集合List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);if (!filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {final Filter filter = filters.get(i);final Invoker<T> next = last;// 將Filter封裝成Invoker,調(diào)用invoke方法時(shí),內(nèi)部鏈?zhǔn)秸{(diào)用下一個(gè)Invoker,最后一個(gè)Invoker就是目標(biāo)服務(wù)類方法的Invokerlast = new Invoker<T>() {@Overridepublic Class<T> getInterface() {return invoker.getInterface();}@Overridepublic URL getUrl() {return invoker.getUrl();}@Overridepublic boolean isAvailable() {return invoker.isAvailable();}@Overridepublic Result invoke(Invocation invocation) throws RpcException {// 這行代碼比較關(guān)鍵,將next作為參數(shù)調(diào)用Filter類方法,在Filter類方法內(nèi)部我們就可以通過(guò)invoker.invoke來(lái)繼續(xù)向下調(diào)用了,最終調(diào)用到真正服務(wù)類方法return filter.invoke(next, invocation);}@Overridepublic void destroy() {invoker.destroy();}@Overridepublic String toString() {return invoker.toString();}};}}return last;} }越靠后的Filter越先執(zhí)行先執(zhí)行,執(zhí)行順序如filter1->filte2->filter3->...->服務(wù)類方法。
2.4:InjvmProtocol
該類是Injvm協(xié)議的實(shí)現(xiàn)類,我們還是從入口方法export開始。
2.4.1:export
源碼如下:
class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);} }主要是創(chuàng)建了InjvmExporter對(duì)象。關(guān)于該對(duì)象,具體參考3:Exporter。
3:Exporter
該接口用于基于相關(guān)協(xié)議來(lái)暴露服務(wù),接口源碼如下:
public interface Exporter<T> {// 獲取內(nèi)部的InvokerInvoker<T> getInvoker();// 取消暴露void unexport(); }主要類圖如下:
接下來(lái)我們從類AbstractExporter開始來(lái)看以下。
3.1:AbstractExporter
源碼如下:
public abstract class AbstractExporter<T> implements Exporter<T> {protected final Logger logger = LoggerFactory.getLogger(getClass());// 內(nèi)部的Invokerprivate final Invoker<T> invoker;// 是否沒(méi)有暴露的標(biāo)記private volatile boolean unexported = false;public AbstractExporter(Invoker<T> invoker) {if (invoker == null)throw new IllegalStateException("service invoker == null");// 必須是接口if (invoker.getInterface() == null)throw new IllegalStateException("service type == null");// 必須有暴露的URLif (invoker.getUrl() == null)throw new IllegalStateException("service url == null");this.invoker = invoker;}@Overridepublic Invoker<T> getInvoker() {return invoker;}// 取消暴露,其實(shí)就是調(diào)用getInvoker().destroy();@Overridepublic void unexport() {if (unexported) {return;}unexported = true;getInvoker().destroy();}@Overridepublic String toString() {return getInvoker().toString();} }3.2:InjvmExporter
AbstractExporter的子類,源碼如下:
class InjvmExporter<T> extends AbstractExporter<T> {// 服務(wù)鍵,一般是服務(wù)接口的全限定類名稱private final String key;// 已經(jīng)暴露的Exporterprivate final Map<String, Exporter<?>> exporterMap;InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {super(invoker);this.key = key;this.exporterMap = exporterMap;exporterMap.put(key, this);}// 取消暴露@Overridepublic void unexport() {super.unexport();exporterMap.remove(key);} }3.3 ListenerExporterWrapper
具有監(jiān)聽功能的Exporter的Wrapper類,源碼如下:
public class ListenerExporterWrapper<T> implements Exporter<T> {private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);private final Exporter<T> exporter;// 注冊(cè)的暴露監(jiān)聽器private final List<ExporterListener> listeners;public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {if (exporter == null) {throw new IllegalArgumentException("exporter == null");}this.exporter = exporter;this.listeners = listeners;// 構(gòu)造函數(shù)執(zhí)行,代表服務(wù)暴露了,執(zhí)行對(duì)應(yīng)的監(jiān)聽器的暴露方法exportedif (listeners != null && !listeners.isEmpty()) {RuntimeException exception = null;for (ExporterListener listener : listeners) {if (listener != null) {try {listener.exported(this);} catch (RuntimeException t) {logger.error(t.getMessage(), t);exception = t;}}}if (exception != null) {throw exception;}}}@Overridepublic Invoker<T> getInvoker() {return exporter.getInvoker();}@Overridepublic void unexport() {// 取消暴露,執(zhí)行監(jiān)聽器的unexported方法try {exporter.unexport();} finally {if (listeners != null && !listeners.isEmpty()) {RuntimeException exception = null;for (ExporterListener listener : listeners) {if (listener != null) {try {listener.unexported(this);} catch (RuntimeException t) {logger.error(t.getMessage(), t);exception = t;}}}if (exception != null) {throw exception;}}}} }ExporterListener參考4:ExporterListener。
4:ExporterListener
源碼如下:
@SPI public interface ExporterListener {// 服務(wù)暴露調(diào)用的方法void exported(Exporter<?> exporter) throws RpcException;// 服務(wù)取消暴露調(diào)用的方法void unexported(Exporter<?> exporter); }類圖如下:
接下來(lái)看下這個(gè)唯一的實(shí)現(xiàn)類ExporterListenerAdapter,如下:
public abstract class ExporterListenerAdapter implements ExporterListener {@Overridepublic void exported(Exporter<?> exporter) throws RpcException {}@Overridepublic void unexported(Exporter<?> exporter) throws RpcException {}}也僅僅是個(gè)空實(shí)現(xiàn),沒(méi)有實(shí)際的邏輯。
總結(jié)
以上是生活随笔為你收集整理的dubbo之服务本地暴露的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 联想服务器x3250 m5文档,【联想x
- 下一篇: 芯洲SCT2230TVBR为用户小型化设