开局一张图,内容全靠编
总的概括

先说一下dubbo的服务端初始化过程
1.在serviceConfig里面组装配置参数;
2.获取到对外提供服务的接口,实现类以及注册url交给ProcxyFactory生成本地代理invoker;当消费者请求过来的时候,最后都是交给invoker去执行,然后invoker通过反射调用真正的实例;
3.生成完本地代理Invoker后,在DubboProtocol中对invoker进行暴露,先将invoker包装生成exporter,再将export作为value,服务接口和端口号组合成key存入到DubboProtocol的map中,这个可以看成是本地注册。
4.在DubboProtocol中生存开启netty通信生存ExchangeServer,ExchangeServer是负责底层通信的,将ip加端口号作为key,ExchangeServer为value存入到map中。ExchangeServer负责相同的ip端口号服务通信。
5.通过invoker里面的url信息获取获取注册中心,将服务者信息注册到注册中心,此节点存储了服务提供方ip、端口、group、接口名称、版本、应用名称,这样可以让消费者从注册中心获取到服务者的信息。
6.为了感知注册中心的一些配置变化,提供者会监听注册中心路径/dubbo/${interfaceClass}/configurators的节点,监听该节点在注册中心的一些配置信息变更。zookeeper注册中心通过zookeeper框架的监听回调接口进行监听(redis注册中心通过订阅命令(subscribe)监听),服务器缓存注册中心的配置,当配置发生变更时,服务会刷新本地缓存,代码在ZookeeperRegistry的doSubscribe方法。

源码分析
下面是dubbo服务端的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd  http://code.alibabatech.com/schema/dubbo  http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">   <!-- 具体的实现bean -->  <bean id="testService" class="com.ts.services.impl.TestServiceImpl" />  <!-- 提供方应用信息,用于计算依赖关系 -->  <dubbo:application name="provider"  />    <!-- 使用zookeeper注册中心暴露服务地址 -->  <dubbo:registry address="zookeeper://127.0.0.1:2181" /><!-- 用dubbo协议在20880端口暴露服务 -->  <dubbo:protocol name="dubbo" port="29014" />  <!-- 声明需要暴露的服务接口 -->  <dubbo:service interface="com.ts.service.TestService" ref="testService" timeout="300"/>
</beans> 

spring会对dubbo:service标签进行解析生成serviceBean,serviceBean实现了ApplicationListener,重写了onApplicationEvent方法,当所有bean刷新完毕后,会调用onApplicationEvent方法,在onApplicationEvent方法中会调用ServiceConfig的export方法
ServiceConfig主要做了三件事
1.组装和检查配置文件里面配置的注册信息,接口信息和协议信息
2.让proxyFactory对要暴露的服务生成Invoker
3.将Invoker交给proctol暴露,开启netty通信,将invoker信息注册到注册中心方便消费者调用,并监听注册中心服务者的配置,
当服务者的配置改变后能动态刷新本地缓存

if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {//配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {exportLocal(url);}//如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){if (logger.isInfoEnabled()) {logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}//当配置的有注册中心的时候if (registryURLs != null && registryURLs.size() > 0&& url.getParameter("register", true)) {for (URL registryURL : registryURLs) {url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));URL monitorUrl = loadMonitor(registryURL);if (monitorUrl != null) {url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());}if (logger.isInfoEnabled()) {logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);}//生成本地代理Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//进行服务暴露和注册Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);}} else {//没有注册中心,直接连接消费者端Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);Exporter<?> exporter = protocol.export(invoker);exporters.add(exporter);}}}

1.生成本地代理Invoker

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

上面是通过proxyFactory对ref,也就是接口的实现类生成代理invoker,proxyFactory默认的是JavassistProxyFactory

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {// TODO Wrapper类不能正确处理带$的类名final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);return new AbstractProxyInvoker<T>(proxy, type, url) {@Overrideprotected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};}

可看到getInvoker方法返回的是一个重写了AbstractProxyInvoker doInvoker方法的实例,在doInvoker方法里调用了wapper.invoker方法,这里wapper.invoker方法运用javassist动态编码技术形成新的代理类。记住这里生成的invoker就是对服务端要暴露的服务的一种代理,这样是为了解耦dubbo和要暴露的服务,让所有客户端的请求先交给Invoker处理,invoker再根据客户端的请求信息利用反射调用真正的服务。

2.对代理进行暴露

 Exporter<?> exporter = protocol.export(invoker);

这里的protocol是通过spi机制动态生成的,它会返回ProtocolListenerWrapper->ListenerExporterWrapper->RegistryProtocol对象链,对于registry协议,两个Wrapper都不会做任何处理,会直接调到RegistryProtocol.export方法。

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {//export invoker,进行暴露,也就是开启了netty通信final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);//registry provider   获取注册中心final Registry registry = getRegistry(originInvoker);final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);//注册服务者节点registry.register(registedProviderUrl);// 订阅override数据// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);//监听服务者在注册中心的配置,当配置改变的时候能刷新本地缓存registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);//保证每次export都返回一个新的exporter实例return new Exporter<T>() {public Invoker<T> getInvoker() {return exporter.getInvoker();}public void unexport() {try {exporter.unexport();} catch (Throwable t) {logger.warn(t.getMessage(), t);}try {registry.unregister(registedProviderUrl);} catch (Throwable t) {logger.warn(t.getMessage(), t);}try {overrideListeners.remove(overrideSubscribeUrl);registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);} catch (Throwable t) {logger.warn(t.getMessage(), t);}}};}

doLocalExport方法

private <T> ExporterChangeableWrapper<T>  doLocalExport(final Invoker<T> originInvoker){String key = getCacheKey(originInvoker);//exporter代理,建立返回的exporter与protocol export出的exporter的对应关系,在override时可以进行关系修改.ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);if (exporter == null) {synchronized (bounds) {exporter = (ExporterChangeableWrapper<T>) bounds.get(key);if (exporter == null) {//声明一个invoker的代理final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));//利用dubboProtocol对invoker进行暴露exporter = new ExporterChangeableWrapper<T>((Exporter<T>)protocol.export(invokerDelegete), originInvoker);bounds.put(key, exporter);}}}return (ExporterChangeableWrapper<T>) exporter;}

进入dubboproctol的export方法

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {//dubbo://202.106.199.34:29014/com.ts.service.TestService?anyhost=true&application=provider&dubbo=2.5.3&interface=com.ts.service.TestService&methods=getName&pid=59736&side=provider&timeout=300&timestamp=1563845476832URL url = invoker.getUrl();// export service.  key的值:com.ts.service.TestService:29014//key其实包括接口名,端口号,以及服务的版本号,分组名String key = serviceKey(url);DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);//将key和export存入map中,这样可以根据客户端发送过来的invocation解析得到取出哪一个exportexporterMap.put(key, exporter);//export an stub service for dispaching eventBoolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);if (isStubSupportEvent && !isCallbackservice){String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){if (logger.isWarnEnabled()){logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +"], has set stubproxy support event ,but no stub methods founded."));}} else {stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);}}openServer(url);return exporter;}

openServer方法,生成一个ExchangeServer,ExchangeServer负责底层通信

private void openServer(URL url) {// find server.  202.106.199.34:29014String key = url.getAddress();//client 也可以暴露一个只有server可以调用的服务。boolean isServer = url.getParameter(Constants.IS_SERVER_KEY,true);if (isServer) {//同一个ip和端口号的服务都交给同一个ExchangeServer处理ExchangeServer server = serverMap.get(key);if (server == null) {serverMap.put(key, createServer(url));} else {//server支持reset,配合override功能使用server.reset(url);}}}

createServer(url)方法

private ExchangeServer createServer(URL url) {//默认开启server关闭时发送readonly事件url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());//默认开启heartbeaturl = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))throw new RpcException("Unsupported server type: " + str + ", url: " + url);url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);ExchangeServer server;try {//开启netty通信server = Exchangers.bind(url, requestHandler);} catch (RemotingException e) {throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);}str = url.getParameter(Constants.CLIENT_KEY);if (str != null && str.length() > 0) {Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();if (!supportedTypes.contains(str)) {throw new RpcException("Unsupported client type: " + str);}}return server;}

先看看requestHandler这个DubboProcto里面的内部类,这个类是处理接受从客户端来的请求,并通过invocation里面所带的参数组长key取出对应的export,再利用export里面的invoker处理请求

private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {public Object reply(ExchangeChannel channel, Object message) throws RemotingException {if (message instanceof Invocation) {Invocation inv = (Invocation) message;//通过invocation获取invokerInvoker<?> invoker = getInvoker(channel, inv);//如果是callback 需要处理高版本调用低版本的问题if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))){String methodsStr = invoker.getUrl().getParameters().get("methods");boolean hasMethod = false;if (methodsStr == null || methodsStr.indexOf(",") == -1){hasMethod = inv.getMethodName().equals(methodsStr);} else {String[] methods = methodsStr.split(",");for (String method : methods){if (inv.getMethodName().equals(method)){hasMethod = true;break;}}}if (!hasMethod){logger.warn(new IllegalStateException("The methodName "+inv.getMethodName()+" not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) +" ,invocation is :"+inv );return null;}}RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());//执行具体的请求return invoker.invoke(inv);}throw new RemotingException(channel, "Unsupported request: " + message == null ? null : (message.getClass().getName() + ": " + message) + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());}.......
}
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException{boolean isCallBackServiceInvoke = false;boolean isStubServiceInvoke = false;int port = channel.getLocalAddress().getPort();String path = inv.getAttachments().get(Constants.PATH_KEY);//如果是客户端的回调服务.isStubServiceInvoke = Boolean.TRUE.toString().equals(inv.getAttachments().get(Constants.STUB_EVENT_KEY));if (isStubServiceInvoke){port = channel.getRemoteAddress().getPort();}//callbackisCallBackServiceInvoke = isClientSide(channel) && !isStubServiceInvoke;if(isCallBackServiceInvoke){path = inv.getAttachments().get(Constants.PATH_KEY)+"."+inv.getAttachments().get(Constants.CALLBACK_SERVICE_KEY);inv.getAttachments().put(IS_CALLBACK_SERVICE_INVOKE, Boolean.TRUE.toString());}//端口号,接口服务,版本号,分组组合成一个keyString serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));//通过key获取到对应的exportDubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);if (exporter == null)throw new RemotingException(channel, "Not found exported service: " + serviceKey + " in " + exporterMap.keySet() + ", may be version or group mismatch " + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress() + ", message:" + inv);return exporter.getInvoker();}

下面看一下dubbo服务端是如何开启netty通信的

server = Exchangers.bind(url, requestHandler);
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {if (url == null) {throw new IllegalArgumentException("url == null");}if (handler == null) {throw new IllegalArgumentException("handler == null");}url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");//getExchanger(url) 获取到HeaderExchanger   //url的值是:dubbo://202.106.199.34:29014/com.ts.service.TestService?anyhost=true&application=provider&channel.readonly.sent=true&codec=dubbo&dubbo=2.5.3&heartbeat=60000&interface=com.ts.service.TestService&methods=getName&pid=59736&side=provider&timeout=300&timestamp=1563845476832return getExchanger(url).bind(url, handler);}

HeaderExchanger的bind方法

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));}

可以看到这里是一个装饰模式的调用链,HeaderExchangeServer--->NettyServer--->DecodeHandler--->HeaderExchangeHandler-->ExchangeHandler。

Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))

返回的是一个nettyservice,这个地方的调用链比想象的要复杂,只能浅尝辄止了。

在开启netty通信后,下面再看服务的注册和监听,RegistryProcotol的export方法

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {//export invokerfinal ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);//registry providerfinal Registry registry = getRegistry(originInvoker);final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);//将服务提供者信息注册到注册中心,方便消费者能在注册中心订阅到服务registry.register(registedProviderUrl);// 订阅override数据// FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);//订阅服务者配置信息,这样当服务者配置信息发生改变的时候,能刷新本地缓存,重新生成exportregistry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);//保证每次export都返回一个新的exporter实例return new Exporter<T>() {public Invoker<T> getInvoker() {return exporter.getInvoker();}public void unexport() {try {exporter.unexport();} catch (Throwable t) {logger.warn(t.getMessage(), t);}try {registry.unregister(registedProviderUrl);} catch (Throwable t) {logger.warn(t.getMessage(), t);}try {overrideListeners.remove(overrideSubscribeUrl);registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);} catch (Throwable t) {logger.warn(t.getMessage(), t);}}};}

最后总结一下dubbo里面比较重要的组建

Invoker
Invoker起到代理处理器的作用,在消费者端,Invoker负责和远程服务端通信
在服务者端,Invoker服务利用反射调用具体的服务实力处理客户端的请求

ProxyFactory
从JavassistProxyFactory就可以看出来,在客户端ProxyFactory会将invoker代理成一个符合客户端要求的实例,比如,客户端要调用远程服务DemoService,那ProxyFactory就会将invoker作为一个被代理的目标生成DemoService代理对象。
在服务端,ProxyFactory会将具体的服务,例如DemoServiceImpl代理成invoker

public class JavassistProxyFactory extends AbstractProxyFactory {@SuppressWarnings("unchecked")public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));}public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {// TODO Wrapper类不能正确处理带$的类名final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);return new AbstractProxyInvoker<T>(proxy, type, url) {@Overrideprotected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};}}

Procotol

@SPI("dubbo")
public interface Protocol {/*** 获取缺省端口,当用户没有配置端口时使用。* * @return 缺省端口*/int getDefaultPort();/*** 暴露远程服务:<br>* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>* * @param <T> 服务的类型* @param invoker 服务的执行体* @return exporter 暴露服务的引用,用于取消暴露* @throws RpcException 当暴露服务出错时抛出,比如端口已占用*/@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;/*** 引用远程服务:<br>* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>* 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>* * @param <T> 服务的类型* @param type 服务的类型* @param url 远程服务的URL地址* @return invoker 服务的本地代理* @throws RpcException 当连接服务提供方失败时抛出*/@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;/*** 释放协议:<br>* 1. 取消该协议所有已经暴露和引用的服务。<br>* 2. 释放协议所占用的所有资源,比如连接和端口。<br>* 3. 协议在释放后,依然能暴露和引用新的服务。<br>*/void destroy();}

procotol通常就是暴露远程服务和引用远程服务,但在RegistryProcotol中的代码,可以看到除了暴露远程服务和引用远程服务外还有将服务注册到注册中心方便订阅,然后监听注册中心,当注册中心里面有什么变化的时候可以刷新本地缓存。
在DubboProctol里面负责客户端底层通信的是ExchangeClient,在服务端负责底层通信的是ExchangeServer

dubbo服务者源码分期相关推荐

  1. dubbo笔记+源码刨析

    会不断更新!冲冲冲!跳转连接 https://blog.csdn.net/qq_35349982/category_10317485.html dubbo笔记 1.概念 RPC全称为remote pr ...

  2. Dubbo源码解析(九)Dubbo系列 源码总结+最近感悟

    从0开始看dubbo源码过程中,对于整个源码阅读与之前阅读部分的Spring源码不太一样,首先之前没有看过源码,因为Spring的一个流程比较复杂,所以一直在跳转跳转,大流程比较多,生命周期较长.自己 ...

  3. Dubbo远程通信源码解析

    前言 前面小编两篇文章重点讲了dubbo协议以及registry协议,协议主要作用就是服务暴露引用,当我们的服务以及暴露和引用并且放到注册中心上去了,那么接下来就要发起远程的通讯.今天小编带来的就是远 ...

  4. dubbo中源码之缓存设计

    该功能采用模板模式设计实现. 原包结构: 类图如下:

  5. dubbo源码学习(四):暴露服务的过程

    dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点, ...

  6. 聊聊Dubbo - Dubbo可扩展机制源码解析

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的实现, ...

  7. Dubbo 源码分析 - SPI 机制

    1.简介 SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以 ...

  8. dubbo源码解析(一)

    大家好,我是烤鸭: 今天和大家分享dubbo的源码解析. 1.SPI http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.ht ...

  9. Dubbo源码分析系列-深入Dubbo SPI机制

    导语   在之前的博客中介绍过关于Java中SPI的机制,也简单的分析了关于Java中SPI怎么去使用.SPI的全称Service Provider Interface,是一种服务发现机制.SPI的本 ...

  10. 一路踩坑构建Dubbo源码

    dubbo构建 源码环境 构建 快速启动 总结 源码环境 随着溪源目前对技术栈的求知欲,也开始入手Dubbo源码啦!!! 构建源码第一步: 必备开发环境:Java 1.5 以上的版本:Maven 2. ...

最新文章

  1. 注释很全的抽象工厂(没用简单工厂优化)
  2. Do a test write by windows live writer
  3. Linux Kernel TCP/IP Stack — L1 Layer — Physical NIC
  4. MySQL的基本查询(二)
  5. 最优化课堂笔记05——一维最优化方法(含重点:黄金分割法)
  6. 4G(LTE)是如何实现智慧农业物联网的?
  7. C++primer第十一章 关联容器 11.1使用关联容器 11.2 关联容器概述
  8. quadprog函数的介绍和应用,二次规划函数
  9. linux如何打开url,用于打开URL的命令?
  10. switch语句(JS)
  11. python 指定版本号
  12. 揭秘2017双11背后的网络-双11的网络产品和技术概览
  13. Windows美化之鼠标光标
  14. visio付款流程图_visio画程序流程图(转)
  15. C++ Primer Plus习题及答案-第五章
  16. Transformers库安装报错
  17. 使用X-NUCLEO-GFX01M1 开发板快速进行 GUI 开发
  18. 微信小程序搜索php,如何解决微信小程序没有模糊搜索功能
  19. 个人总结的一个VMP脱壳步骤.
  20. 宿华和程一笑,说一说快手背后的两个男人!

热门文章

  1. 数字图像处理冈萨雷斯版学习(二)
  2. JMeter下载和安装
  3. php实现商城评论,谁能写一个thinkphp 商城购物评论回复能例子?
  4. 快启动win10pe制作详细图文教程
  5. cmmi实践访谈测试ppt_CMMI3_实践篇.ppt
  6. Xposed框架安装失败
  7. 小程序版聊天室|聊天小程序|仿微信聊天界面小程序
  8. unity开发下的C#学习笔记——第四章:鼠标匀速跟随
  9. 室内设计数据手册pdf_室内设计制图讲座 PDF扫描版[25MB]
  10. 使用exceljs导出excel表格