深度解析dubbo服务本地引用(injvm)

本文基于dubbo v2.6.1

1. API方式使用dubbo

我们可以看下dubbo官网api使用方式使用dubbo,地址:链接,我们可以在文档的服务消费者模块看到
在这里插入图片描述
new了一个ReferenceConfig 然后通过get方法获取xxx接口的实现类,也就是服务代理。接下来我们就看这个com.alibaba.dubbo.config.ReferenceConfig#get

2.com.alibaba.dubbo.config.ReferenceConfig#get

//  获取
    public synchronized T get() {
        // 已经销毁
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        // 还没有初始化的时候进行初始化
        if (ref == null) {
            // 初始化
            init();
        }
        return ref;
    }

首先是该方法synchronized 修饰,然后判断销毁标识,判断ref 是否是空,这个ref其实就是ReferenceConfig范型的实现类,也就是下图的xxxService
在这里插入图片描述
如果是null的话就走init方法,第一次的话肯定是要走这里的。我们看下这个init方法。

3.com.alibaba.dubbo.config.ReferenceConfig#init

该方法前面是一些参数的配置有判断,我们主要是把目光放到后半部分
在这里插入图片描述
后面有个createProxy(map)方法,然后返回了一个该接口的实现类,也就是我们的代理类。

4.com.alibaba.dubbo.config.ReferenceConfig#createProxy

URL tmpUrl = new UR("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {// 如果配置里面没有配置这个scope 或者injvm
      if (url != null && url.length() > 0) { // if a url is specified, don't do local reference
          isJvmRefer = false;
      } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
          // by default, reference local service if there is
          isJvmRefer = true;
      } else {
          isJvmRefer = false;
      }
} else {
  isJvmRefer = isInjvm().booleanValue();
}
if (isJvmRefer) {// jvm
	  URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
	  invoker = refprotocol.refer(interfaceClass, url);
	  if (logger.isInfoEnabled()) {
	      logger.info("Using injvm service " + interfaceClass.getName());
	  }
} else {
  ...
}

第一行new 了一个tempUrl,后面的就是判断要不要injvm,也就是本地引用,如果我们这里配置了scope=injvm或者injvm=true就会执行到isJvmRefer = isInjvm().booleanValue(); 也就isJvmRefer 这个变量设置成true。
接着就是执行进isJvmRefer =true这个代码段中,首先是封装一个url,其中protocol是injvm,host是127.0.0.1。接着调用invoker = refprotocol.refer(interfaceClass, url);这句,我们先看下这个refprotocol 对象,
在这里插入图片描述
很显然是Protocol接口对象,根据dubbo spi 扩展点的自适应特性,会在url找protocol属性的值,这里url的protocol属性值是injvm,也就是
com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
在这里插入图片描述
我们接着看下com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol 的refer方法

5. com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol#refer

 @Override
 public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
      return new InjvmInvoker<T>(serviceType, url, url.getServiceKey(), exporterMap);
 }

这里new了一个InjvmInvoker 类并且返回 ,参数分别是 1.咱们那个接口class 类,2.url ,3. 接口的全类名,4. exporterMap 缓存了本地暴露的服务,这个exporterMap 对象,我们在本地服务暴露的时候,最后将serviceKey 与exporter缓存了exporterMap 这个map中。
这个InjvmInvoker 先放这,我们回到com.alibaba.dubbo.config.ReferenceConfig#createProxy 这个方法继续看

6.com.alibaba.dubbo.config.ReferenceConfig#createProxy



// 判断check属性, 默认是check的
Boolean c = check;
if (c == null && consumer != null) {
    c = consumer.isCheck();
}
if (c == null) {
    c = true; // default true
}

// 进行check
if (c && !invoker.isAvailable()) {
    // make it possible for consumer to retry later if provider is temporarily unavailable
    initialized = false;
    throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
    logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
// create service proxy
return (T) proxyFactory.getProxy(invoker);

这里首先是判断了用户配置的check属性,如果用户没有配置默认是需要check的,接着就是check=true的话进行check,check失败设置初始化=false 然后抛出异常。
接着就是(T) proxyFactory.getProxy(invoker); 这句话,创建service代理对象。
其实这个proxyFactory 我们在服务暴露的时候见过了,我们再来看下
在这里插入图片描述
其实根据dubbo spi 扩展点自适应特性,当你指定proxy属性值的时候 用你指定的实现,如果没有话使用默认的实现,这里也就是javassist,JavassistProxyFactory
在这里插入图片描述
我们看下JavassistProxyFactory 类

7.com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getProxy

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

这里是我那 Proxy.getProxy 生成的一个代理类。
比如说我这个接口是这个样子的:


public interface IHelloProviderService {
    String getName(Integer id);
}

生成的代理类

package com.alibaba.dubbo.common.bytecode;

import com.alibaba.dubbo.common.bytecode.ClassGenerator;
import com.alibaba.dubbo.rpc.service.EchoService;
import com.xuzhaocai.dubbo.provider.IHelloProviderService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class proxy0
implements ClassGenerator.DC,
EchoService,
IHelloProviderService {
    public static Method[] methods;
    private InvocationHandler handler;

    @Override
    public String getName(Integer n) {
        Object[] arrobject = new Object[]{n};
        Object object = this.handler.invoke(this, methods[0], arrobject);
        return (String)object;
    }

    public Object $echo(Object object) {
        Object[] arrobject = new Object[]{object};
        Object object2 = this.handler.invoke(this, methods[1], arrobject);
        return object2;
    }
    public proxy0() {
    }
    public proxy0(InvocationHandler invocationHandler) {
        this.handler = invocationHandler;
    }
}

我们可以看到Proxy生成的代理类实现那个接口,然后在实现接口方法里面将参数封装到数据里面,然后调用了invocationHandler的invoke方法。我们看下这个invocationHandler 。

8.com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler

/**
 * InvokerHandler
 */
public class InvokerInvocationHandler implements InvocationHandler {
    private final Invoker<?> invoker;
    public InvokerInvocationHandler(Invoker<?> handler) {
        this.invoker = handler;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }
}

在这里invocationHandler对象里面invoker就是咱们前面InjvmInvoker 对象。我们可以看到这个InvokerInvocationHandler#invoke 方法里面帮我们获取了执行的方法名与方法参数类型,如果是toString,hashCode,equals 方法直接调用InjvmInvoker 的,并将方法与参数封装成一个RpcInvocation ,然后调用了InjvmInvoker 的invoke方法,
我们先来看下new RpcInvocation(method, args);
在这里插入图片描述
我们看下这个InjvmInvoker。

9. com.alibaba.dubbo.rpc.protocol.injvm.InjvmInvoker

这个InjvmInvoker 继承 AbstractInvoker ,然后AbstractInvoker 实现Invoker 接口,实现了Result invoke(Invocation invocation) throws RpcException;方法,与 Class<T> getInterface();方法。

class InjvmInvoker<T> extends AbstractInvoker<T> {
    private final String key;
    private final Map<String, Exporter<?>> exporterMap;
    InjvmInvoker(Class<T> type, URL url, String key, Map<String, Exporter<?>> exporterMap) {
        super(type, url);
        this.key = key;
        this.exporterMap = exporterMap;
    }
    @Override
    public boolean isAvailable() {
        InjvmExporter<?> exporter = (InjvmExporter<?>) exporterMap.get(key);
        if (exporter == null) {
            return false;
        } else {
            return super.isAvailable();
        }
    }
    @Override
    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);
    }
}

我们看到invoke方法没有在InjvmInvoker类中,我们看下它的父类中invoke方法:

 @Override
    public Result invoke(Invocation inv) throws RpcException {
        // if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
        if (destroyed.get()) {
            logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
                    + ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
        }

        RpcInvocation invocation = (RpcInvocation) inv;
        invocation.setInvoker(this);//设置invoker ,将自己设置进去
        if (attachment != null && attachment.size() > 0) {
            invocation.addAttachmentsIfAbsent(attachment);
        }
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            /**
             * invocation.addAttachmentsIfAbsent(context){@link RpcInvocation#addAttachmentsIfAbsent(Map)}should not be used here,
             * because the {@link RpcContext#setAttachment(String, String)} is passed in the Filter when the call is triggered
             * by the built-in retry mechanism of the Dubbo. The attachment to update RpcContext will no longer work, which is
             * a mistake in most cases (for example, through Filter to RpcContext output traceId and spanId and other information).
             */
            invocation.addAttachments(contextAttachments);
        }
        if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) {
            invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
        }
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);


        try {
            return doInvoke(invocation);
        } catch (InvocationTargetException e) { // biz exception
            Throwable te = e.getTargetException();
            if (te == null) {
                return new RpcResult(e);
            } else {
                if (te instanceof RpcException) {
                    ((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
                }
                return new RpcResult(te);
            }
        } catch (RpcException e) {
            if (e.isBiz()) {
                return new RpcResult(e);
            } else {
                throw e;
            }
        } catch (Throwable e) {
            return new RpcResult(e);
        }
    }

可以看到前面判断了一下销毁没有,然后从Rpc上下文中get到一些kv然后设置进去。我们看后面一句doInvoke(invocation);,这个才是真正的调用,子类实现了这个方法,这里也就是咱们的InjvmInvoker类

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

首先InjvmProtocol.getExporter(exporterMap, getUrl());这行代码其实是从exporterMap 这个缓存中获取我们这个url中的服务名对应的实现类,我们可以回想下,我们在本地服务暴露的时候,然后以serviceKey为key ,exporter为value缓存到了这个map中,如果获取的这个exporter是null的话抛出异常,如果不是null的话,往RpcContext中设置RemoteAddress 的ip是127.0.0.1 ,port是0。最后调用了exporter.getInvoker().invoke(invocation);,我们可以回顾下当初服务暴露的时候invoker是这样子创建的:
在这里插入图片描述
然后最后调用的是wrapper.invokeMethod的方法。我们再来看下这个wrapper的结构是啥样子的:

public class Wrapper$1 {


    public static String[] pns;// 字段名
    public static Map pts;//<字段名,字段类型>
    public static String[] mns;//方法名
    public static String[] dmns;//自己方法的名字

    public static Class[] mts;//方法参数类型

    public String[] getPropertyNames(){ return pns; }
    public boolean hasProperty(String n){ return pts.containsKey(n); }


    public Class getPropertyType(String n){ return (Class)pts.get(n); }

    public String[] getMethodNames(){ return mns; }
    public String[] getDeclaredMethodNames(){ return dmns; }




    public void setPropertyValue(Object o, String n, Object v){

        com.xuzhaocai.dubbo.provider.IHelloProviderService w;
        try{
            w = (( com.xuzhaocai.dubbo.provider.IHelloProviderService)$1);
        }catch(Throwable e) {
            throw new IllegalArgumentException(e);
        }
        if( $2.equals("字段名")){
            w."字段名"= $3;
            return ;
        }
    }


    public Object getPropertyValue(Object o, String n){
        com.xuzhaocai.dubbo.provider.IHelloProviderService w;
        try{
            w = (( com.xuzhaocai.dubbo.provider.IHelloProviderService)$1);
        }catch(Throwable e){
            throw new IllegalArgumentException(e);
        }
        if( $2.equals("字段名")){
            return ($w) w."字段名";

        }

        return null;

    }

    public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws InvocationTargetException{

        com.xuzhaocai.dubbo.provider.IHelloProviderService w;
        try{
             w = (( com.xuzhaocai.dubbo.provider.IHelloProviderService)$1);
        }catch(Throwable e){
            throw new IllegalArgumentException(e);
        }
        try{
            if("方法名".equals($2)  && 方法参数个数 == $3.length  &&  $3[1].getName().equals("方法第几个参数的name")){
                w.方法名(参数);
            }

            if("方法名".equals($2)  && 方法参数个数 == $3.length  &&  $3[1].getName().equals("方法第几个参数的name")){
                w.方法名(参数);
            }
        } catch(Throwable e) {
            throw new java.lang.reflect.InvocationTargetException(e);
        }

        throw new NoSuchMethodException("Not found method "+$2+" in class 你传进来那个实现类");

    }
}

其实到最后就是调用的服务提供者的方法
在这里插入图片描述
本地服务暴露有想了解的可以读下《深度解析dubbo服务本地暴露(injvm)》这篇文章。

猜你喜欢

转载自blog.csdn.net/yuanshangshenghuo/article/details/106949565