provider端當`scope`不為`remote`時,都需要將服務發布到本地,發布到本地時使用的時injvm協議,是指服務器發布方和引用方的程序在同一個jvm中執行。
而consumer端中,從2.2.0開始,每個服務默認都會在本地暴露。在引用服務的時候,默認優先引用本地服務。如果希望引用遠程服務可以使用一下配置強制引用遠程服務。
```
<dubbo:reference ... scope="remote" />
```
## 流程
injvm協議的過程是dubbo中最簡單的協議,但除了沒有注冊中心外,還是可以對其基本流程進行全面的了解。

## 1.發布到injvm協議
發布到invjm協議時,會先修改url的協議,host和port
```
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL)
.setHost(LOCALHOST)
.setPort(0);
```
然后,ExtensionLoader動態獲取Protocol接口時,使用的是InjvmProtocol,會被包裝為
```
|- ProtocolFilterWrapper
|- ProtocolListenerWrapper
|- InjvmProtocol
```
ProtocolFilterWrapper:主要用來生成調用鏈,內部的buildInvokerChain方法會查找Filter的實現類,查找group為provider的,并根據order排序,將這些Filter連接成一個調用鏈 InvokerChain,最終調用已經生成的Invoker
```
EchoFilter -> ClassloaderFilter -> GenericFilter ->
ContextFilter -> TraceFilter -> TimeoutFilter ->
MonitorFilter -> ExceptionFilter -> AbstractProxyInvoker類
```
ProtocolListenerWrapper:主要用來添加監聽事件。首先調用InjvmProtocol的export,直接返回InjvmExporter;之后查找ExporterListener的實現類,最后將這兩者封裝為ListenerExporterWrapper并返回。
InjvmProtocol中定義了一個exporterMap,內部保存了服務名與Exporter的對應關系
到此為止,就已經發布到本地了。
## 2. 引用
如果未強制指定遠程服務,會首先從本地查找服務,查找的入口是Spring的context的getBean方法獲取實例,其會通過兩個步驟獲取指定接口的實例:創建Invoker和獲取proxy代理對象。
```
Invoker<DemoService> consuerInvoker = protocol.refer(DemoService.class, url);
DemoService service = proxyFactory.getProxy(consuerInvoker);
```
在引用服務流程中,我們已經分析了獲取Proxy對象的過程,每個協議獲取Invoker的方式不同,對于injvm協議來說,protocol被包裝為:
```
|- ProtocolFilterWrapper
|- ProtocolListenerWrapper
|- InjvmProtocol
```
ProtocolFilterWrapper和ProtocolListenerWrapper的功能還是構建Filter鏈和添加監聽事件,不同的是,對ProtocolFilterWrapper來說,consumer使用的refer方法,引用`group = Constants.CONSUMER`,而provider端使用的是export,使用了`group = Constants.PROVIDER`,這些都是在實現類的注解中進行配置。
對ProtocolListenerWrapper來說,provider使用的`exporter.listener`,而consumer使用的`invoker.listener`
InjvmProtocol的refer中會創建一個InjvmInvoker并返回
```
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);
}
```
InjvmInvoker內部記錄了Class類型,url,服務名和exporterMap(保存了服務名與exporter對象的映射)
```
public Result doInvoke(Invocation invocation) throws Throwable {
Exporter<?> exporter = InjvmProtocol.getExporter(exporterMap, getUrl());
if (exporter == null) {
throw new RpcException("Service [" + key + "] not found.");
}
RpcContext.getContext().setRemoteAddress(NetUtils.LOCALHOST, 0);
return exporter.getInvoker().invoke(invocation);
}
```
## 測試程序
了解了injvm協議的發布和引用過程后,我們可以對這個過程進行測試,在測試中,我們加入一對Listener和一對Filter。
PExporterListener實現了ExporterListener,在provider發布服務時使用
```
public class PExporterListener implements ExporterListener {
public void exported(Exporter<?> exporter) throws RpcException {
System.out.println("[PExporterListener][exported]:" + exporter.getInvoker().getUrl());
}
public void unexported(Exporter<?> exporter) {
System.out.println("[PExporterListener][unexported]" + exporter.getInvoker().getUrl());
}
}
```
CExporterListener實現了InvokerListener,在consumer調用時使用
```
public class CExporterListener implements InvokerListener {
public void referred(Invoker<?> invoker) throws RpcException {
System.out.println("[CExporterListener][referred]:" + invoker.getUrl());
}
public void destroyed(Invoker<?> invoker) {
System.out.println("[CExporterListener][destroyed]:" + invoker.getUrl());
}
}
```
PFilter實現了Filter接口,注解@Activate指定了在provider端使用
```
@Activate(group = Constants.PROVIDER, order = -120000)
public class PFilter implements Filter{
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("[PFilter][invoke]" + invocation.getMethodName());
return invoker.invoke(invocation);
}
}
```
CFilter實現了Filter接口,注解@Activate指定了在consumer端使用
```
@Activate(group = Constants.CONSUMER, order = -120000)
public class CFilter implements Filter{
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println("[CFilter][invoke]" + invocation.getMethodName());
return invoker.invoke(invocation);
}
}
```
下面是測試程序:
```
public static void main(String[] args) {
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getDefaultExtension();
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
// 創建url
URL url = new URL("injvm","127.0.0.1",0);
url= url.setPath(DemoService.class.getName());
url = url.addParameter(Constants.EXPORTER_LISTENER_KEY, "provider.listener");
url = url.addParameter(Constants.INVOKER_LISTENER_KEY, "consumer.listener");
// 發布服務1.創建export invoker
Invoker<DemoService> invoker = proxyFactory.getInvoker(new DemoServiceImpl(),DemoService.class,url);
// 發布服務2.發布到injvm協議
Exporter<DemoService> exporter = protocol.export(invoker);
// 可以取消發布,會拋出異常
// exporter.unexport();//Service [com.alibaba.dubbo.demo.DemoService] not found.
// 引用服務1.創建ref Invoker
Invoker<DemoService> consuerInvoker = protocol.refer(DemoService.class, url);
// 引用服務2.創建代理對象
DemoService service = proxyFactory.getProxy(consuerInvoker);
System.out.println(service.sayHello("world")); // 執行調用
}
```
輸出為:
```
[04/12/17 09:44:15:015 CST] main INFO logger.LoggerFactory: using logger: com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter
[PExporterListener][exported]:injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?exporter.listener=provider.listener&invoker.listener=consumer.listener
[CExporterListener][referred]:injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?exporter.listener=provider.listener&invoker.listener=consumer.listener
[CFilter][invoke]sayHello
[PFilter][invoke]sayHello
Hello world
```