写在前面

源码 。

服务提供者是标记了@Service注解的类,想要被服务消费者使用,必须将服务暴露出去,即让服务消费者拿到封装服务信息的com.alibaba.dubbo.common.URL对象字符串,当前有三种服务暴露方式:

远程暴露:即将服务信息注册到远端注册中心,如配置<dubbo:service scope="remote" />。
本地暴露:JVM内部调用,因为信息已经在内存中,通过内存可以直接获取调用信息,因此叫做本地暴露,如配置<dubbo:service scope="local">。
不暴露:不暴露服务,可以忽略这种方式,如配置<dubbo:service scope="none">。

本文来分享的是本地暴露,相关的源码在模块dubbo-rpc-injmv中,如下图:

在dubbo之服务提供者配置 一文中,我们其实分析了部分服务暴露的内容,大家可以看下,本文为了承接,会有部分内容的重叠,就从方法com.alibaba.dubbo.config.ServiceConfig.doExportUrls来开始分析。

1:doExportUrls

源码如下:

class FakeCls {private void doExportUrls() {// 2022-01-21 18:25:43List<URL> registryURLs = loadRegistries(true);// 循环所有的协议暴露服务到所有的注册中心地址// 协议:protocols,即<dubbo:protocol>设置// 服务:通过<dubbo:service>设置// 注册中心地址:registryURLs,通过<dubbo:registry>指定for (ProtocolConfig protocolConfig : protocols) {// 2022-01-21 18:34:22doExportUrlsFor1Protocol(protocolConfig, registryURLs);}}
}

2022-01-21 18:25:43处获取配置的所有注册中心地址,具体参考1.1:loadRegistries2022-01-21 18:34:22处是将服务按照指定的协议注册到注册中心,具体参考1.2:doExportUrlsFor1Protocol

1.1:loadRegistries

本文讲解的时本地服务暴漏,不会使用到这里的信息,但是为了完整性,放在这里,对这部分感兴趣的朋友可以参考dubbo之服务远程暴露 文章分析。

1.2:doExportUrlsFor1Protocol

将服务按照指定的协议注册到注册中心,分为远程暴漏和本地暴漏,其中本地暴漏不会注册服务到注册中心,因为是同一个JVM,信息可以直接从JVM中获取到,因为本文重点分析的是本地服务暴漏,所以关于远程暴漏的相关源码会选择性忽略,关于这部分的分析,可以才参考dubbo之服务远程暴露 文章分析。源码如下:

class FakeCls {private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {// 协议的名称,如<dubbo:protocol name="dubbo" port="20826">,这里就是dubbo// 协议:就是暴漏自己的方法String name = protocolConfig.getName();// 没有则默认使用dubboif (name == null || name.length() == 0) {name = "dubbo";}//*** 省略构建URL相关代码 ***//// 获取scope,如果是本地暴漏的话配置如:<dubbo:service interface="dongshi.daddy.service.scopelocal.ScopeLocalService" ref="scopeLocalService" scope="local"/>// 在文章开头也提到了可配置为remote,代表远程暴漏,none代表不爆露String scope = url.getParameter(Constants.SCOPE_KEY);// 如果是配置scope="none",不进行任何操作,此时不进行暴漏,即不对外使用if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {// 如果是scope不是remote则使用本地服务暴漏if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {// 2022-01-22 12:25:51exportLocal(url);}// 如果是scope不是local则使用远程服务暴漏if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {// *** 省略远程服务暴漏逻辑 *** //}}// 添加暴漏服务urlthis.urls.add(url);}
}

2022-01-22 12:25:51处是本地服务暴漏,具体参考1.3:exportLocal

1.3:exportLocal

源码如下:

class FakeCls {private void exportLocal(URL url) {// url.getProtocol():一般是dubbo // Constants.LOCAL_PROTOCOL:injvm// 为什么做这个判断???if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {// 构建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&timestamp=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// 将服务类的信息存储到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");}}
}

该小节以下部分稍微有点绕,大家吃耐心,不懂的话,多看几遍!!!

2022-01-22 17:21:22处protocol定义为private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();,可以看到是获取自适应扩展类 ,其中从Protocol接口也可以看出来,源码如下:

/*** 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方法是标注了@Adaptive 注解的,此处protocol
Protocol#Adaptive,这个很好理解,因为获取就是动态生成的自适应子类,通过其调用真正的扩展实现类,那么想要知道调用的到底是谁就需要知道生成的代码到是什么样子的,我们可以通过如下的步骤来获取其内容:

在com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtensionClass中的代码ClassLoader classLoader = findClassLoader();添加条件变量"code.contains("Protocol$Adaptive")",然后再次运行程序,就可以停止在这里,获取code的内容了。

如下是我获取的内容:

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);}
}

我们重点关注其中的export方法,可以看到是调用url.getProtocol()作为目标扩展类的名称,那么是什么值呢?我们的url为injvm://127.0.0.1/...可以看到协议是injvm,那么对应的扩展类是谁呢,可以从文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中找到答案,其中key为injvm的的配置项内容是injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,因此最终调用的扩展类类是com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol,但是真的是这样吗?我们来debug看一下,如下图:

从图中可以看出,还分别调用了QosProtocolWrapperProtocolListenerWrapper,ProtocolFilterWrapper,这是Protocol的Wrapper类,我们从META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中可以看出来,如下图:

关于Wrapper详细可以参考dubbo之SPI Wrapper分析 。

最终调用过程为Protocol$Adaptive->QosProtocolWrapper->ProtocolListenerWrapper->ProtocolFilterWrapper->InjvmProtocol。具体的我们在2:Protocol分析。

2:Protocol

源码如下:

@SPI("dubbo")
public interface Protocol {// 获取当前协议在没有配置端口时的默认端口号int getDefaultPort();// 暴漏service供远程调用// 1:协议对象需要在收到一个请求后记录远程源的的地址,通过API RpcContext.getContext().setRemoteAddress()// 2:该方法必须具备幂等性(idempotent [aɪ'dempətənt]),即通过该方法调用一次或者是多次来暴漏一个URL没有任何差别// 3:Invoker实例需要被框架传入进来,protoco扩展类需要用到,如自适应时使用// 返回值: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() {// 因为没有标注@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!");}// 因为没有标注@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 {// 以下远端暴漏才会执行,这里可以忽略,因为url是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:28protocol.export(invoker)继续调用装饰的protocol类,这里调用的就是ProtocolFilterWrapper,关于该类参考2.3:ProtocolFilterWrapperCollections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))处是使用keyexporter.listener,从url获取值从而获取要激活的ExporterListener扩展类。ListenerExporterWrapper构造函数源码如下:

class FakeCls {public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {}
}

该监听器的作用是用来监听Exporter暴漏完毕和取消暴漏完毕。

2.3:ProtocolFilterWrapper

主要用于给Invoker增加Filter过滤器链,在调用真正的服务方法之前会调用过滤器Filter的逻辑,具体参考2.3.1:export

2.3.1:export

源码如下:

class FakeCls {@Overridepublic <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {// 此处要求协议是registry://,即远程暴露,这里是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:53buildInvokerChain添加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扩展类集合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,调用invoke方法时,内部链式调用下一个Invoker,最后一个Invoker就是目标服务类方法的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 {// 这行代码比较关键,将next作为参数调用Filter类方法,在Filter类方法内部我们就可以通过invoker.invoke来继续向下调用了,最终调用到真正服务类方法return filter.invoke(next, invocation);}@Overridepublic void destroy() {invoker.destroy();}@Overridepublic String toString() {return invoker.toString();}};}}return last;}
}

越靠后的Filter越先执行先执行,执行顺序如filter1->filte2->filter3->...->服务类方法

2.4:InjvmProtocol

该类是Injvm协议的实现类,我们还是从入口方法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);}
}

主要是创建了InjvmExporter对象。关于该对象,具体参考3:Exporter

3:Exporter

该接口用于基于相关协议来暴露服务,接口源码如下:

public interface Exporter<T> {// 获取内部的InvokerInvoker<T> getInvoker();// 取消暴露void unexport();
}

主要类图如下:

接下来我们从类AbstractExporter开始来看以下。

3.1:AbstractExporter

源码如下:

public abstract class AbstractExporter<T> implements Exporter<T> {protected final Logger logger = LoggerFactory.getLogger(getClass());// 内部的Invokerprivate final Invoker<T> invoker;// 是否没有暴露的标记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;}// 取消暴露,其实就是调用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> {// 服务键,一般是服务接口的全限定类名称private final String key;// 已经暴露的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

具有监听功能的Exporter的Wrapper类,源码如下:

public class ListenerExporterWrapper<T> implements Exporter<T> {private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);private final Exporter<T> exporter;// 注册的暴露监听器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;// 构造函数执行,代表服务暴露了,执行对应的监听器的暴露方法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() {// 取消暴露,执行监听器的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 {// 服务暴露调用的方法void exported(Exporter<?> exporter) throws RpcException;// 服务取消暴露调用的方法void unexported(Exporter<?> exporter);
}

类图如下:

接下来看下这个唯一的实现类ExporterListenerAdapter,如下:

public abstract class ExporterListenerAdapter implements ExporterListener {@Overridepublic void exported(Exporter<?> exporter) throws RpcException {}@Overridepublic void unexported(Exporter<?> exporter) throws RpcException {}}

也仅仅是个空实现,没有实际的逻辑。

dubbo之服务本地暴露相关推荐

  1. dubbo源码分析7 之 服务本地暴露

    在上一篇文章我们分析了一下 dubbo 在服务暴露发生了哪些事,今天我们就来分析一下整个服务暴露中的本地暴露.(PS:其实我感觉本地暴露蛮鸡肋的).本地暴露需要服务提供方与服务消费方在同一个 JVM. ...

  2. 一句话解释Dubbo服务本地暴露和远程暴露

    本地暴露是暴露在JVM仲,不需要网络通信.每个服务默认都会在本地暴露.在引用服务的时候,优先引用本地服务. 远程暴露是将IP,端口等信息暴露给远程客户端,调用时需要网络通信

  3. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  4. 深入解析 Dubbo 3.0 服务端暴露全流程

    简介:随着云原生时代的到来,Dubbo 3.0 的一个很重要的目标就是全面拥抱云原生.正因如此,Dubbo 3.0 为了能够更好的适配云原生,将原来的接口级服务发现机制演进为应用级服务发现机制. 作者 ...

  5. Dubbo服务端暴露全流程

    本文来说下Dubbo服务端暴露全流程 文章目录 概述 什么是应用级服务发现 服务端暴露全流程 暴露injvm协议的服务 注册service-discovery-registry协议 暴露Triple协 ...

  6. 阿里面试官:你知道Dubbo的服务暴露机制么?

    点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...

  7. Dubbo 服务本地缓存

    开篇 根据官方图,dubbo调用者需要通过注册中心(例如:ZK)注册信息,获取提供者,但是如果频繁往ZK获取信息,肯定会存在单点故障问题,所以dubbo提供了将提供者信息缓存在本地的方法. Dubbo ...

  8. dubbo 支持服务降级吗_dubbo面试题!会这些,说明你真正看懂了dubbo源码

    整理了一些dubbo可能会被面试的面试题,感觉非常不错.如果你基本能回答说明你看懂了dubbo源码,对dubbo了解的足够全面.你可以尝试看能不能回答下.我们一起看下有哪些问题吧? dubbo中&qu ...

  9. dubbo k8s 服务发现_将Dubbo微服务迁移到k8s集群环境中前的思考与落地

    将Dubbo微服务迁移到k8s中的思考与落地 说到容器化,不得不提kubernetes这个集群编排系统,它是一个开源系统,用于容器化应用的自动部署.扩缩和管理. Kubernetes 将构成应用的容器 ...

  10. Dubbo之服务导入流程解析

    Dubbo之服务导入流程解析 接着上回<Dubbo之服务暴露流程浅析>,上一篇文章已经介绍完了Dubbo的服务提供者的服务暴露的整个流程,本文主要介绍Dubbo服务消费者的服务导入流程. ...

最新文章

  1. javascript语言学习
  2. 变量、中文-「译」javascript 的 12 个怪癖(quirks)-by小雨
  3. 【学习笔记】启动Nginx、查看nginx进程、查看nginx服务主进程的方式、Nginx服务可接受的信号、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级
  4. G6 图可视化引擎——简介
  5. 我什么计算机作文600字,我家的电脑作文600字
  6. CVP(Critical Value Pruning)illustration with clear principle in details
  7. 使用网桥模式(bridge networking mode)配置KVM-QUME虚拟机网络
  8. python requests编码的问题_python requests 编码问题
  9. mysql 基本操作和问题
  10. 转:标准C++中的string类的用法总结
  11. Web前端的学习路线到底是什么,看完秒懂!
  12. 云擎供应链:为中小仓库提供SaaS供应链解决方案
  13. 学生成绩管理系统设计java_java学生成绩管理系统设计与实现
  14. EL表达式和JSTL标签库
  15. java openxml word_C#采用OpenXml实现给word文档添加文字
  16. PS制作水印的简单教程
  17. Keras的loss_weights和class_weight
  18. python 雷达图像识别_Python 新一代多普勒天气雷达基数据可视化
  19. 为什么工厂要实现自动化、智能化?
  20. 家用计算机的普及英语作文,优秀高二英语作文:计算机

热门文章

  1. WWW 2022 | 搜索广告CVR延迟反馈建模DEFUSE
  2. SpringCloud2020 学习笔记(十五)Spring Cloud Eureka 自我保护机制 关闭
  3. 第五届强网杯全国网络安全挑战赛writeup
  4. QTP Reporter类封装
  5. python异常处理时所使用的保留字_【2020年12月计算机二级Python语言考试冲刺题(二)】- 环球网校...
  6. c语言输出实心心矩形,c语言打印空白星号矩形
  7. blendshapes
  8. 重磅干货:30张图读懂当前中国金融体系!
  9. mysql基于PHP下的大学生校园交流论坛的设计与实现 毕业设计源码101634
  10. 交换机和路由器技术-07-静态路由配置