目录
前提
JDK实现SPI
Dubbo实现SPI
Dubbo源码
1. 找到Dubbo的命名空间处理类,也就是Dubbo的入口类
2. 将dubbo标签交给spring进行管理,就是从 BeanDefinition----> Bean的过程。
3. 服务暴露
4. 服务引入
总结
仿写Dubbo
前提
1. Dubbo源码是深度依赖Spring的,因此了解Spring源码是分析Dubbo的前提,否则会很吃力。
2. SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件
JDK实现SPI
说的直白点,SPI就是面向接口编程。就是创建接口的全限定名称的文件,然后在文件中配置好这个接口的实现类,然后代码中就可以获取到这个接口的、配置好的所有的实现类。JDK是已经提供了实现SPI思想的实现类:ServiceLoader。
瞄一眼源码:
下面演示使用JDK提供的类实现SPI的代码:
1、首先配置接口的全限定名称:
接口:
package com.enjoy.spi.tt;
public interface P {
public void sayHello();
}
实现类P1
package com.enjoy.spi.tt;
public class P1 implements P {
@Override
public void sayHello() {
System.out.println("hello P1");
}
}
实现类P2
package com.enjoy.spi.tt;
public class P2 implements P {
@Override
public void sayHello() {
System.out.println("hello P2");
}
}
测试类:
以上就是JDK提供的SPI实现方式,很强大,但也有不足。比如:实现类很多,我们每次都要拿到所有的实现类,无法精确定位到具体的某一个。
Dubbo实现SPI
基于JDK提供的SPI实现的缺陷,Dubbo进行了补强。下面,我只会分析部分核心补强功能,为我们下面Dubbo源码分析提供一些铺垫。
Dubbo提供的实现SPI思想的类: ExtensionLoader。
配置全限定接口文件:
接口也需要打上对应的Dubbo注解
P3实现类:
package com.enjoy.spi.tt2;
public class P3 implements PP {
@Override
public void sayHello() {
System.out.println("hello P3");
}
}
P4:
package com.enjoy.spi.tt2;
public class P4 implements PP {
@Override
public void sayHello() {
System.out.println("hello P4");
}
}
测试类:
Dubbo源码
基于xml形式解读dubbo源码,其实相对而言还是非常清晰的。
1. 找到Dubbo的命名空间处理类,也就是Dubbo的入口类
2. 将dubbo标签交给spring进行管理,就是从 BeanDefinition----> Bean的过程。
3. 服务暴露
接口:
package com.enjoy.service;
import com.enjoy.entity.OrderEntiry;
public interface OrderService {
OrderEntiry getDetail(String id);//目标对象,方法,参数
}
实现类需要打上Dubbo的@Service注解
package com.enjoy.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.enjoy.entity.OrderEntiry;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderServiceImpl implements OrderService {
@Override
public OrderEntiry getDetail(String id) {
System.out.println(super.getClass().getName()+"被调用一次:"+System.currentTimeMillis());
OrderEntiry orderEntiry = new OrderEntiry();
orderEntiry.setId("O0001");
orderEntiry.setMoney(1000);
orderEntiry.setUserId("U0001");
return orderEntiry;
}
}
配置xml
<?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:context="http://www.springframework.org/schema/context"
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://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--全局配置-->
<dubbo:provider timeout="3000" />
<!-- 服务提供方应用名称, 方便用于依赖跟踪 -->
<dubbo:application name="busi-server"/>
<!-- 使用本地zookeeper作为注册中心 -->
<dubbo:registry address="zookeeper://192.168.0.105:2181" />
<!--name指示使用什么协议监听端口:dubbo/rmi/rest-->
<dubbo:protocol id="d1" name="dubbo" port="20880" />
<dubbo:protocol id="d2" name="dubbo" port="20882" />
<!-- 通过xml方式配置为bean, 让spring托管和实例化 -->
<bean id="orderService" class="com.enjoy.service.OrderServiceImpl"/>
<!-- 声明服务暴露的接口,并暴露服务 -->
<dubbo:service interface="com.enjoy.service.OrderService" ref="orderService" protocol="d1" />
</beans>
所有的Dubbo都会交给Spring进行管理,生成对应的BeanDefinition。而服务暴露的核心流程,我们只需要关注以上的接口和实现类实例化Bean的过程即可。核心方法还是initializeBean。在分析Spring源码的时候,这个方法已经强调过很多次了。
下面开启debug,关注xml文件中配置的
<dubbo:service interface="com.enjoy.service.OrderService" ref="orderService" />
逐步debug来到 afterPropertiesSet方法处,而这个方法被Dubbo的子类ServiceBean重写。
也就是说ServiceBean会把服务端的Bean进行封装,像
<dubbo:provider timeout="3000" delay="1"/>
<dubbo:application name="busi-server" />
<dubbo:registry address="zookeeper://192.168.0.105:2181" />
<dubbo:protocol name="dubbo" port="20882" />
这些标签对应的Bean对象。在我们处理需要暴露的服务(OrderService)的时候,都会被封装进OrderService对应的ServiceBean对象中。
下面再来看看服务暴露的详细内容:
进入方法:
这个方法核就是2件事:
1. 拿到xml文件中配置的信息
2. 根据配置的协议进行服务暴露:
继续进行服务暴露的底层代码: doExportUrlsFor1Protocol方法很长,直接跳转到核心代码处,核心逻辑就是将ServiceBean对象包装成Invoker对象进行暴露
至此,服务暴露分析完毕。再往下就是更为底层的网络协议的内容的,没有研究过,不做分析。
4. 服务引入
通过上诉代码分析,我们知道服务暴露的最直接的代码是在ServiceBean的 afterPropertiesSet方法中。 其实,服务引入就是 ReferenceBean 的 afterPropertiesSet方法中:
核心代码:
其实,服务端也会实例化对应的ReferenceBean对象,而这个对象的ref指向的是一个代理对象。
那么这个代理对象是如何生成的呢?
进入 createProxy 方法,我们发现它还是干了2件事情:
1. 根据接口信息和URL信息生成Invoker对象,服务暴露的时候也是生成Invoker对象进行暴露的,正好对应。
2. 协议根据invoker对象,生成一个代理对象。
至此,消费端对象引入分析完毕。
总结
Dubbo的服务注册与发现机制,核心动作就2个。protocol.export(serviceInvoker
) 将服务给暴露出去, protocol.refer(DemoService.class, registryUrl)将网络上的
服务引入进来。
1. 服务端启动,我们会将配置好的Dubbo需要暴露的Bean对象生成ServiceBean,然后包装成Invoker对象发送到注册中心。
2. 消费端引入到invoker对象,会先生成一个ReferenceBean对象,而这个对象的ref会指向一个代理对象,这个代理对象是有Invoker生成的。
3.消费端调用的时候,实际上最终调用的是这个代理对象,而代理对象会直接刷锅给invoker对象,由网络处理消费端的invoker对象。
4. 服务端会根据invoker对象的实际处理逻辑,调用到服务端的具体业务逻辑进行相应的处理。
个人觉得,Dubbo就是一个增强版的RPC而已。
仿写Dubbo
既然是仿写,肯定是仿写核心流程,我们就不再需要zookeeper了。直接写一个服务端通过网络协议暴露出去,服务端直接引入即可。
1. dubbo暴露的对象使用的是invoker,那么我们也需要使用这个invoker对象。
2. Dubbo的服务注册与发现机制,核心动作就2个。protocol.export(serviceInvoker
) 将服务给暴露出去, protocol.refer(DemoService.class, registryUrl)将网络上的
服务引入进来。那么我们肯定也是要从重写协议,至少需要export和refer这两个方法的。
下面开始仿照重写:
Dubbo最终的Invoker对象:
咱们也仿写Invoker:
package com.enjoy.protocol;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.*;
import com.enjoy.service.DemoService;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* SimpleInvoker.
*/
public class SimpleInvoker<T> implements Invoker<T> {
private T target;
private Class<T> type;
private URL url;
public SimpleInvoker(T service, Class<T> type, URL url){
this.target = service;
this.type = type;
this.url = url;
}
@Override
public Class<T> getInterface() {
return type;
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Method method = null;
try {
method = type.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
return new RpcResult(method.invoke(target, invocation.getArguments()));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public URL getUrl() {
return url;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void destroy() {
}
}
Dubbo协议比较复杂,咱就写个最简单的
package com.enjoy.protocol;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.protocol.rmi.RmiRemoteInvocation;
import com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteInvocation;
import org.springframework.remoting.support.RemoteInvocationFactory;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
/**
* RmiProtocol.
*/
public class RmiProtocol implements Protocol {
public static final int DEFAULT_PORT = 1099;
@Override
public int getDefaultPort() {
return DEFAULT_PORT;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//创建spring rmi服务
final RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
rmiServiceExporter.setRegistryPort(invoker.getUrl().getPort());
rmiServiceExporter.setServiceName(invoker.getUrl().getPath());
rmiServiceExporter.setServiceInterface(invoker.getInterface());
//此时目标服务没有,需要通过invoker调通,使用代理
T service = (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{invoker.getInterface()},
new InvokerInvocationHandler(invoker));
rmiServiceExporter.setService(service);//DemoService service,如果能拿它,直接设入就ok
try {
rmiServiceExporter.afterPropertiesSet();
} catch (RemoteException e) {
throw new RpcException(e.getMessage(), e);
}
return null;
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new SimpleInvoker(doRefer(type,url),type,url);
}
public <T> T doRefer(Class<T> type, URL url) throws RpcException {
final RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
if (url.getParameter(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()).equals(Version.getProtocolVersion())) {
rmiProxyFactoryBean.setRemoteInvocationFactory(new RemoteInvocationFactory() {
@Override
public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
return new RmiRemoteInvocation(methodInvocation);
}
});
}
rmiProxyFactoryBean.setServiceUrl(url.toIdentityString());
rmiProxyFactoryBean.setServiceInterface(type);
rmiProxyFactoryBean.setCacheStub(true);
rmiProxyFactoryBean.setLookupStubOnStartup(true);
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
rmiProxyFactoryBean.afterPropertiesSet();
return (T) rmiProxyFactoryBean.getObject();
}
@Override
public void destroy() {
}
}
测试类
package com.enjoy;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler;
import com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory;
import com.enjoy.protocol.RmiProtocol;
import com.enjoy.protocol.SimpleInvoker;
import com.enjoy.service.DemoService;
import com.enjoy.service.DemoServiceImpl;
import com.enjoy.util.ProtocolUtil;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Proxy;
public class RpcProtocolTest_01 {
URL url = URL.valueOf("rmi://127.0.0.1:9001/"+ DemoService.class.getName());
/**
* Protocol连接服务端invoker
* 将目标服务调用信息,包装成为invoker实体,暴露到网络上
*
* 当网络信息到达,将触发invoker的invoke方法,最终将调用转到目标service上
*/
@Test
public void provider() throws IOException {
DemoService service = new DemoServiceImpl();
Invoker<DemoService> invoker = new SimpleInvoker(service,DemoService.class,url);
Protocol protocol = new RmiProtocol();
//暴露对象
protocol.export(invoker);
System.out.println("Dubbo server 启动");
// 保证服务一直开着
System.in.read();
}
/**
* Protocol连接消费端invoker
* 将要调用的信息,包装成invoker实体,向网络发送
*
* 本地调用接口代理时,最终方法被转到invoker的invoke方法上,向网络发送
*/
@Test
public void consumer() {
//协议
Protocol protocol = new RmiProtocol();
//消费invoker,负责发送协议调用信息
Invoker<DemoService> invoker = protocol.refer(DemoService.class, url);
//做一个动态代理,将调用目标指向invoker即可
DemoService service = (DemoService)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{invoker.getInterface()},
new InvokerInvocationHandler(invoker));//反射逻辑
String result = service.sayHello("peter");
System.out.println(result);
}
}