Dubbo消费者代理的创建
在消费者端,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消费者代理的创建相关推荐
- Dubbo消费者代理的调用
当消费者调用服务接口的方法时,实际调用的是接口代理类的InvokerInvocationHandler的invoke()方法. public Object invoke(Object proxy, M ...
- Dubbo(七)使用SpringBoot搭建dubbo消费者工程
本章将创建一个dubbo 消费者工程并实现远程调用消费者示例.环境:springboot + dubbo + zookeeper, 工程目录如下: 主要步骤: 创建maven工程添加pom依赖 创建S ...
- 【设计模式】代理模式 ( 动态代理使用流程 | 创建目标对象 | 创建被代理对象 | 创建调用处理程序 | 动态创建代理对象 | 动态代理调用 )
文章目录 前言 一.静态代理的弊端 二.动态代理的优势 三.动态代理使用流程 1.目标对象接口 2.被代理对象 3.调用处理程序 4.客户端 四.动态生成 代理对象 类 的 字节码 文件数据 前言 代 ...
- dubbo消费者与提供者之间的tcp长连接
摘要 dubbo消费者同提供者之间的tcp连接是长连接形式,连接由消费方建立随机端口主动向提供者的dubbo端口发起连接请求,一旦连接建立,除非服务停止.网络异常,否则双方不会主动关闭tcp连接.也就 ...
- dubbo消费者请求不到生产者
com.alibaba.dubbo.rpc.RpcException: Invoke remote method timeout. method: 将dubbo服务器.dubbo提供者.dubbo消费 ...
- dubbo 消费者也要暴露端口吗_一文详细解读 Dubbo 中的 http 协议
(给ImportNew加星标,提高Java技能) 转自:Kirito的技术分享,作者:kiritomoe 太阳红彤彤,花儿五颜六色,各位读者朋友好,又来到了分享 Dubbo 知识点的时候了.说到 Du ...
- Dubbo消费者服务的订阅
在消费者通过refer()方法获得Invoker的时候同时完成对于相关方法的订阅.以默认的dubbo作为注册中心为例子. 当消费者调用refer()方法的时候由于当中的Url中的protocol为re ...
- dubbo 消费者重复订阅 @Reference注入不进Controller
问题 环境配置 spring根application-context.xml.spring mvc的servlet-applicationcontext.xml引入了同一份dubbo.xml 描述 应 ...
- Spring Boot集成Dubbo多模块项目创建与配置
目录 概述 使用工具 环境搭建 1.父模块创建 2.创建子模块 多模块项目配置 一. 父模块pom配置 1.继承设置 2.使用dependencyManagement管理依赖版本号 3.使用prope ...
最新文章
- Linux之编辑器 vim
- 微服务架构核心20讲 课程的学习笔记
- 计算机技术开发如何做账,研发支出是什么类科目?怎么做账?
- PowerDesigner生成注释以及对应数据库的sql语句
- PHP使用Switch语句判断星座,PHP的switch判断语句的“高级”用法详解 用switch语句怎样判断成绩的等级...
- eclipse3.4 SVN插件安装
- cifar-10 图像转为jpg
- [Windows Phone 7]开发分享图片的插件(2)
- mfc ctabctrl 双排显示_盐城便宜的开口型双排脚手架生产厂家-斯戴博盘扣脚手架...
- 禅道项目管理——bug管理工具
- 应用笔记3816 ds 1302 估算涓流充电实时时钟的超级电容备份时间
- SIM7600透传模式
- 【历史上的今天】3 月 11 日:谷歌推出 Google Voice;互联网先驱诞生日;Foursquare 上线
- 电磁场与电磁波 面电流和体电流磁感应强度的计算
- linux编写多时区时间显示程序,Linux系统时区时间修改
- 长安大学微型计算机原理与接口技术答案,长安大学微机原理与接口技术B卷答案...
- Java中如果被除数是零时的异常处理
- MySQL的varchar水真的太深了——InnoDB记录存储结构
- Numeric 数据类型
- 【详解】Linux面试详解