在消费者端,dubbo通过AnnotationBean类实现了BeanPostProcessor接口用来对beanFactory的中bean进行相应的处理。

关于消费者的bean以及bean中@Reference注解的处理在AnnotationBean的postProcessBeforeInitialization()方法当中。

对于bean中采用了@Reference注解的属性的处理在下面这段代码中。

Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {try {if (! field.isAccessible()) {field.setAccessible(true);}Reference reference = field.getAnnotation(Reference.class);if (reference != null) {Object value = refer(reference, field.getType());if (value != null) {field.set(bean, value);}}} catch (Throwable e) {logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);}
}

在这里将会遍历bean当中所有的属性,当找到带有@Reference注解的属性的时候,马上会尝试通过refer()方法完成对于@Reference的属性类型类的代理生成,以便在下面的代码中通过set()方法将代理了相应所需要代理的方法的代理set()进bean相应的的属性上。

看到实现同样实现在AnnotationBean类中的refer()方法。

String interfaceName;
if (! "".equals(reference.interfaceName())) {interfaceName = reference.interfaceName();
} else if (! void.class.equals(reference.interfaceClass())) {interfaceName = reference.interfaceClass().getName();
} else if (referenceClass.isInterface()) {interfaceName = referenceClass.getName();
} else {throw new IllegalStateException("The @Reference undefined interfaceClass or interfaceName, and the property type " + referenceClass.getName() + " is not a interface.");
}
String key = reference.group() + "/" + interfaceName + ":" + reference.version();
ReferenceBean<?> referenceConfig = referenceConfigs.get(key);

在refer()方法的一开始,会根据@Reference的配置,或者本身配置了@Reference注解属性的实现接口来确定这个属性的接口名称(优先选择配置在注解当中的接口属性),因此将会构造成这个ReferenceBean的key(由reference的组名,接口名称以及版本号组成),如果在这里之前的bean已经构造过相同key的referenceBean,那么可以直接将直接的referenceBean取出而不用重新创建新的referenceBean来创建代理。

当然,如果没有通过key取得到相应的referenceBean的话,将会在下面的代码当中重新创建referenceBean,并根据Spring所提供的的上下文将referenceBean所需要的属性配置在ReferenceBean当中。

if (reference.consumer() != null && reference.consumer().length() > 0) {referenceConfig.setConsumer((ConsumerConfig)applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
}
if (reference.monitor() != null && reference.monitor().length() > 0) {referenceConfig.setMonitor((MonitorConfig)applicationContext.getBean(reference.monitor(), MonitorConfig.class));
}
if (reference.application() != null && reference.application().length() > 0) {referenceConfig.setApplication((ApplicationConfig)applicationContext.getBean(reference.application(), ApplicationConfig.class));
}
if (reference.module() != null && reference.module().length() > 0) {referenceConfig.setModule((ModuleConfig)applicationContext.getBean(reference.module(), ModuleConfig.class));
}
if (reference.consumer() != null && reference.consumer().length() > 0) {referenceConfig.setConsumer((ConsumerConfig)applicationContext.getBean(reference.consumer(), ConsumerConfig.class));
}
try {referenceConfig.afterPropertiesSet();
} catch (RuntimeException e) {throw (RuntimeException) e;
} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);
}

以上面的代码为例子,如果这@Reference注解当中配置了相应的监控中心消费者等属性,都将会在这里尝试从spring的上下文当中去取得相应的bean注入到referenceBean当中。

在这之后调用referenceBean的afterPropertiesSet()方法。

在afterPropertiesSet()方法中,首先是对在@Reference注解当中并没有进行配置的属性进行设置。以Registry(注册中心)为例子。

if ((getRegistries() == null || getRegistries().size() == 0)&& (getConsumer() == null || getConsumer().getRegistries() == null || getConsumer().getRegistries().size() == 0)&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);if (registryConfigMap != null && registryConfigMap.size() > 0) {List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();for (RegistryConfig config : registryConfigMap.values()) {if (config.isDefault() == null || config.isDefault().booleanValue()) {registryConfigs.add(config);}}if (registryConfigs != null && registryConfigs.size() > 0) {super.setRegistries(registryConfigs);}}
}

如果在之前的注解当中并没有配置注册中心或者在referenceBean的消费者配置以及应用配置中都没有配置上注册中心的属性,那么将会遍历Spring上下文当中所有RegistryConfig类配置在ReferenceBean的超类ReferenceConfig当中。

在这个方法中,针对其他属性,例如moniter监控中心的操作与这里的注册中心的操作完全类似。

之后在确认所有属性配置完毕,并且消费者也已经初始化完毕之后,将会通过getObject()方法尝试取得referenceBean当中的ref属性,而这个属性恰恰就是代理了referenceBean当中消费者所要调用方法的代理类。

public Object getObject() throws Exception {return get();
}
public synchronized T get() {if (destroyed){throw new IllegalStateException("Already destroyed!");}if (ref == null) {init();}return ref;
}

由上文可见,当第一次调用getObject()方法的时候将会毫无疑问的调用ReferenceConfig的init()方法。

在init()方法的开头,仍旧是检察ReferenceBean的属性的配置是否已经完毕,并在这里对之前方法仍旧没有配置的属性进行配置。同时针对ReferenceBean的接口以及所要实现代理的方法进行验证,该方法是否是该接口下面的方法。

if (methods != null && methods.size() > 0) {for (MethodConfig methodBean : methods) {String methodName = methodBean.getName();if (methodName == null || methodName.length() == 0) {throw new IllegalStateException("<dubbo:method> name attribute is required! Please check: <dubbo:service interface=\"" + interfaceClass.getName() + "\" ... ><dubbo:method name=\"\" ... /></<dubbo:reference>");}boolean hasMethod = false;for (java.lang.reflect.Method method : interfaceClass.getMethods()) {if (method.getName().equals(methodName)) {hasMethod = true;break;}}if (!hasMethod) {throw new IllegalStateException("The interface " + interfaceClass.getName()+ " not found method " + methodName);}}
}

在上面的代码中确保了所要代理的方法一定是接口所声明的方法。

Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (! isGeneric()) {String revision = Version.getVersion(interfaceClass, version);if (revision != null && revision.length() > 0) {map.put("revision", revision);}String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();if(methods.length == 0) {logger.warn("NO method found in service interface " + interfaceClass.getName());map.put("methods", Constants.ANY_VALUE);}else {map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);

在接下来,将会构造两个hashMap,其中的map将会存放是实现代理的必要属性,也将是之后生成代理的重要参数。

在这里,消费者的身份属性,版本属性,创建时间,进程id都将在这里被设置在map当中。如果没有才用泛化引用,在这里会给非动态类的将要被代理的接口生产相应的wrapper。

public static Wrapper getWrapper(Class<?> c){while( ClassGenerator.isDynamicClass(c) ) // can not wrapper on dynamic class.c = c.getSuperclass();if( c == Object.class )return OBJECT_WRAPPER;Wrapper ret = WRAPPER_MAP.get(c);if( ret == null ){ret = makeWrapper(c);WRAPPER_MAP.put(c,ret);}return ret;}

可以看到,在这里会给接口的父类创建wrapper,如果父类直接是Object,那么直接会返回默认的ObjectWrapper,但是如果父类不是,那么将会通过makeWrapper()方法动态生成wrapper。

Wrapper通过包装目标类,动态根据目标类生成相应的get和set方法。拿field属性来做例子。

StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");for( Field f : c.getFields() )
{String fn = f.getName();Class<?> ft = f.getType();if( Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()) )continue;c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");pts.put(fn, ft);
}

在这里动态根据目标类的属性生成了相应的针对被包装类的get和set方法用来在接下里的操作可以方便取得目标的属性。同理被包装类的方法,经过wrapper的包装,方法的取得也动态生成了相应的方法。

在通过wrapper取得相应的方法之后,将所有的方法通过逗号隔开组成新的字符串放在map当中。

同时,在AbstractConfig类中,给出了appendParameters()方法。

protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {if (config == null) {return;}Method[] methods = config.getClass().getMethods();for (Method method : methods) {try {String name = method.getName();if ((name.startsWith("get") || name.startsWith("is")) && ! "getClass".equals(name)&& Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0&& isPrimitive(method.getReturnType())) {Parameter parameter = method.getAnnotation(Parameter.class);if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {continue;}int i = name.startsWith("get") ? 3 : 2;String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), ".");String key;if (parameter != null && parameter.key() != null && parameter.key().length() > 0) {key = parameter.key();} else {key = prop;}Object value = method.invoke(config, new Object[0]);String str = String.valueOf(value).trim();if (value != null && str.length() > 0) {if (parameter != null && parameter.escaped()) {str = URL.encode(str);}if (parameter != null && parameter.append()) {String pre = (String)parameters.get(Constants.DEFAULT_KEY + "." + key);if (pre != null && pre.length() > 0) {str = pre + "," + str;}pre = (String)parameters.get(key);if (pre != null && pre.length() > 0) {str = pre + "," + str;}}if (prefix != null && prefix.length() > 0) {key = prefix + "." + key;}parameters.put(key, str);} else if (parameter != null && parameter.required()) {throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");}} else if ("getParameters".equals(name)&& Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0&& method.getReturnType() == Map.class) {Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);if (map != null && map.size() > 0) {String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : "");for (Map.Entry<String, String> entry : map.entrySet()) {parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue());}}}} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);}}
}

在这个方法中,将会遍历目标类当中的is和get方法,并通过反射获得相应的值,如果在该方法的上面实现了@Parameter注解,那么还将会对相应的值进行url编码,在获得相应的值之后通过注解当中配置的key或者属性名加上默认的前缀,作为键值对存放在map中,同样作为创建代理的参数存放在map中。在具体的参数获得中,application,Consumer,module以及该referenceBean和目标接口下的get和is方法都会把属性存放在map中,在init()方法的最后通过createProxy()方法,将Map作为参数,获取代理。

在createProxy()方法当中,首先,会根据是否配置了url来确定是否配置在了一开始的ReferenceBean当中,如果没有,则判断本地是否有接口暴露,如果有则采用本地服务。

这里默认不是本地服务,而是配置了注册中心往下走。

如果没有配置url在referenceBean当中,则会loadRegistries()当中url的数组。

protected List<URL> loadRegistries(boolean provider) {checkRegistry();List<URL> registryList = new ArrayList<URL>();if (registries != null && registries.size() > 0) {for (RegistryConfig config : registries) {String address = config.getAddress();if (address == null || address.length() == 0) {address = Constants.ANYHOST_VALUE;}String sysaddress = System.getProperty("dubbo.registry.address");if (sysaddress != null && sysaddress.length() > 0) {address = sysaddress;}if (address != null && address.length() > 0 && ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {Map<String, String> map = new HashMap<String, String>();appendParameters(map, application);appendParameters(map, config);map.put("path", RegistryService.class.getName());map.put("dubbo", Version.getVersion());map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));if (ConfigUtils.getPid() > 0) {map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));}if (! map.containsKey("protocol")) {if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {map.put("protocol", "remote");} else {map.put("protocol", "dubbo");}}List<URL> urls = UrlUtils.parseURLs(address, map);for (URL url : urls) {url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());url = url.setProtocol(Constants.REGISTRY_PROTOCOL);if ((provider && url.getParameter(Constants.REGISTER_KEY, true))|| (! provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {registryList.add(url);}}}}}return registryList;
}

在loadRegistries方法中,首先会遍历所有的registryConfig,优先配置url为配置文件的url,如果没有则为本身所配置的url。接下里跟之前准备创建代理的参数的map一样,在这里类似的跟之前的操作一样构造url以及map。具体的url构造在urlUtils中实现。这里不展开。

在根据注册中心构造完毕url之后,也会根据是否配置了监控中心的url,添加监控中心的url属性,最后,所有参数将会被构造成get的形式作为最后的url保存下来。

在下面,每一个生成的url都会生成对应的invoker。

List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {invokers.add(refprotocol.refer(interfaceClass, url));if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {registryURL = url; // 用了最后一个registry url}
}

假如这里采用了dubbo协议,这里将会生成dubbo invoker。

这里生成的invoker将在方法最后最后在ProxyFactory当中通过getProxy()获得最后所需要的代理对象并返回。

在AbstractProxyFactory当中的getProxy()方法中。

public <T> T getProxy(Invoker<T> invoker) throws RpcException {Class<?>[] interfaces = null;String config = invoker.getUrl().getParameter("interfaces");if (config != null && config.length() > 0) {String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);if (types != null && types.length > 0) {interfaces = new Class<?>[types.length + 2];interfaces[0] = invoker.getInterface();interfaces[1] = EchoService.class;for (int i = 0; i < types.length; i ++) {interfaces[i + 1] = ReflectUtils.forName(types[i]);}}}if (interfaces == null) {interfaces = new Class<?>[] {invoker.getInterface(), EchoService.class};}return getProxy(invoker, interfaces);
}

在这里,在除了定义在了invoker中的接口外,还将会固定将所代理的接口以及EchoService接口加在代理的类型当中。

接下来以jdk创建代理为例子,直接就在JdkProxyFactory中的getProxy()方法通过jdk实现了Invoker以及接口实现了关于Invoker的代理创建。

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

参数与一般的jdk代理实现并无区别,只是Invoker在最后还是作为参数用来还是生成被代理鄂最终类InvokerInvocationHandler。

消费者的代理创建就此结束。

Dubbo消费者代理的创建相关推荐

  1. Dubbo消费者代理的调用

    当消费者调用服务接口的方法时,实际调用的是接口代理类的InvokerInvocationHandler的invoke()方法. public Object invoke(Object proxy, M ...

  2. Dubbo(七)使用SpringBoot搭建dubbo消费者工程

    本章将创建一个dubbo 消费者工程并实现远程调用消费者示例.环境:springboot + dubbo + zookeeper, 工程目录如下: 主要步骤: 创建maven工程添加pom依赖 创建S ...

  3. 【设计模式】代理模式 ( 动态代理使用流程 | 创建目标对象 | 创建被代理对象 | 创建调用处理程序 | 动态创建代理对象 | 动态代理调用 )

    文章目录 前言 一.静态代理的弊端 二.动态代理的优势 三.动态代理使用流程 1.目标对象接口 2.被代理对象 3.调用处理程序 4.客户端 四.动态生成 代理对象 类 的 字节码 文件数据 前言 代 ...

  4. dubbo消费者与提供者之间的tcp长连接

    摘要 dubbo消费者同提供者之间的tcp连接是长连接形式,连接由消费方建立随机端口主动向提供者的dubbo端口发起连接请求,一旦连接建立,除非服务停止.网络异常,否则双方不会主动关闭tcp连接.也就 ...

  5. dubbo消费者请求不到生产者

    com.alibaba.dubbo.rpc.RpcException: Invoke remote method timeout. method: 将dubbo服务器.dubbo提供者.dubbo消费 ...

  6. dubbo 消费者也要暴露端口吗_一文详细解读 Dubbo 中的 http 协议

    (给ImportNew加星标,提高Java技能) 转自:Kirito的技术分享,作者:kiritomoe 太阳红彤彤,花儿五颜六色,各位读者朋友好,又来到了分享 Dubbo 知识点的时候了.说到 Du ...

  7. Dubbo消费者服务的订阅

    在消费者通过refer()方法获得Invoker的时候同时完成对于相关方法的订阅.以默认的dubbo作为注册中心为例子. 当消费者调用refer()方法的时候由于当中的Url中的protocol为re ...

  8. dubbo 消费者重复订阅 @Reference注入不进Controller

    问题 环境配置 spring根application-context.xml.spring mvc的servlet-applicationcontext.xml引入了同一份dubbo.xml 描述 应 ...

  9. Spring Boot集成Dubbo多模块项目创建与配置

    目录 概述 使用工具 环境搭建 1.父模块创建 2.创建子模块 多模块项目配置 一. 父模块pom配置 1.继承设置 2.使用dependencyManagement管理依赖版本号 3.使用prope ...

最新文章

  1. Linux之编辑器 vim
  2. 微服务架构核心20讲 课程的学习笔记
  3. 计算机技术开发如何做账,研发支出是什么类科目?怎么做账?
  4. PowerDesigner生成注释以及对应数据库的sql语句
  5. PHP使用Switch语句判断星座,PHP的switch判断语句的“高级”用法详解 用switch语句怎样判断成绩的等级...
  6. eclipse3.4 SVN插件安装
  7. cifar-10 图像转为jpg
  8. [Windows Phone 7]开发分享图片的插件(2)
  9. mfc ctabctrl 双排显示_盐城便宜的开口型双排脚手架生产厂家-斯戴博盘扣脚手架...
  10. 禅道项目管理——bug管理工具
  11. 应用笔记3816 ds 1302 估算涓流充电实时时钟的超级电容备份时间
  12. SIM7600透传模式
  13. 【历史上的今天】3 月 11 日:谷歌推出 Google Voice;互联网先驱诞生日;Foursquare 上线
  14. 电磁场与电磁波 面电流和体电流磁感应强度的计算
  15. linux编写多时区时间显示程序,Linux系统时区时间修改
  16. 长安大学微型计算机原理与接口技术答案,长安大学微机原理与接口技术B卷答案...
  17. Java中如果被除数是零时的异常处理
  18. MySQL的varchar水真的太深了——InnoDB记录存储结构
  19. Numeric 数据类型
  20. 【详解】Linux面试详解

热门文章

  1. Java学习之路之Hello World小程序
  2. 外部js php变量,在外部JS文件中包含PHP变量?
  3. Kai - Golang实现的目标检测云服务
  4. Python面向对象之反射
  5. python 判断当前系统的Python编译器类型
  6. linux文件删除恢复
  7. 国内域名商.wang总量统计TOP10:新网居亚 地位不稳
  8. 替换 centOS6.5 默认安装的旧版 firefox ,安装最新版 firefox 全过程
  9. 利用锁分析器进行线程竞争检测
  10. 仿真器和模拟器的区别是什么?