排查了3个多小时,因为一个简单的错误,发现一个强大的参数解析工具,记录一下。

背景

Nodejs 通过 tether 调用 Java Dubbo 服务。请求类的某个参数对象 EsCondition 有 fieldName, op, value 三个字段,value 的参数值正确解析, fieldName, op 的参数值解析为 null 。 深入到 Dubbo 源码进行单步调试定位到,发现字段 fieldName, op 缺少 setter 方法。 加上之后就正常了。

JavaBean约定

定位到问题后,有一种如梦方醒的感觉。问题就是这么简单,可是却花了很多时间。如果我注意到一个类里有 a, b, c 三个字段, c 解析成功了,有 getter/setter 方法,而 b, c 没有 setter 方法,通过仔细对比或许就可以解决这个问题。

事实上, Java 与框架进行交互有个基本的约定: JavaBean 约定, JavaBean 中的每个实例属性都有 setter/getter 方法。完整的约定如下:

  • 有一个 public 默认构造器(尤其要有无参构造器);
  • 属性设置成 private, 使用 public 的 getter,setter 方法访问,同时getter,setter 方法与属性名也需要对应。例如属性 name,getter 方法就要写成 public String getName(){ return name; }
  • 实现序列化接口。

如果缺乏默认构造器,那么在构造 Java 入参对象会有问题; 如果缺失 getter / setter ,在设置或获取入参对象的字段的值就会有问题。事实上,很多框架都使用了反射的方式,通过 getter/setter 方法来操作 JavaBean 中的字段来进行操作。因此,保证 JavaBean 约定,是与众多框架进行交互的重要前提。 而要保证 JavaBean 约定,只要自动生成 setter/getter 方法,或者在类上增加一个 @Getter, @ Setter, 或 @Data 注解即可。JavaBeans 的百科: “JavaBeans”

约定胜于配置。 建立一些约定进行简单处理,往往比灵活而复杂处理各种配置更加容易理解。 读者可阅: “约定优于配置”

PojoUtils

这里有个强大的参数解析类 com.alibaba.dubbo.common.utils.PojoUtils,用于将嵌套的 Map 转换成嵌套的对象。特别实用。PoJoUtils 与 CompatibleTypeUtils, ReflectUtils, ClassHelper 联合实现了这个功能。 这里先给出一个测试用例。

package shared.util;

import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import shared.utils.PojoUtils;
import zzz.study.apidesign.export.common.Condition;
import zzz.study.apidesign.export.common.ExportParam;
import zzz.study.apidesign.export.common.SearchParam;
import zzz.study.patterns.composite.escondition.Match;
import zzz.study.patterns.composite.escondition.Op;
import zzz.study.patterns.composite.escondition.Range; import java.util.Arrays;
import java.util.Objects; public class PojoUtilsTest { @Test
public void testRealize() {
ExportParam exportParam = new ExportParam();
exportParam.setBizType("order");
exportParam.setCategory("baozhengjin");
exportParam.setSource("wsc");
exportParam.setRequestId("request"+System.currentTimeMillis());
exportParam.setExtra(ImmutableMap.of("account", "qin"));
exportParam.setStrategy("standard");
SearchParam searchParam = new SearchParam();
searchParam.setBizId(123L);
searchParam.setStartTime(151456798L);
searchParam.setEndTime(153456789L);
searchParam.setConditions(Arrays.asList(
new Condition("name", Op.eq, "sisi"),
new Condition("pay_time", Op.range, new Range(152987654L, 153987654)),
new Condition("state", Op.in, Arrays.asList(5,6)),
new Condition("goods_title", Op.match, new Match("走过路过不要错过", "100%"))
));
exportParam.setSearch(searchParam); Object paramGeneralized = PojoUtils.generalize(exportParam); ExportParam exportParamRestored = (ExportParam) PojoUtils.realize(paramGeneralized, ExportParam.class);
assert Objects.equals(exportParamRestored, exportParam); }
}

generalize 将一个对象转换成 Map,在 dubbo 服务端,realize 将 Map 还原为对象。

代码实现

读者可以引用 dubbo 2 以上版本,查看 com.alibaba.dubbo.common.utils.PojoUtils 这个类的实现。里面分不同情况进行了解析,同时使用了递归技术来对嵌套的结果进行解析。

/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.common.utils; import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap; /**
* PojoUtils. Travel object deeply, and convert complex type to simple type.
* <p>
* Simple type below will be remained:
* <ul>
* <li> Primitive Type, also include <b>String</b>, <b>Number</b>(Integer, Long), <b>Date</b>
* <li> Array of Primitive Type
* <li> Collection, eg: List, Map, Set etc.
* </ul>
* <p>
* Other type will be covert to a map which contains the attributes and value pair of object.
*
* @author william.liangf
* @author ding.lid
*/
public class PojoUtils { private static final ConcurrentMap<String, Method> NAME_METHODS_CACHE = new ConcurrentHashMap<String, Method>();
private static final ConcurrentMap<Class<?>, ConcurrentMap<String, Field>> CLASS_FIELD_CACHE =
new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Field>>(); public static Object[] generalize(Object[] objs) {
Object[] dests = new Object[objs.length];
for (int i = 0; i < objs.length; i ++) {
dests[i] = generalize(objs[i]);
}
return dests;
} public static Object[] realize(Object[] objs, Class<?>[] types) {
if (objs.length != types.length)
throw new IllegalArgumentException("args.length != types.length");
Object[] dests = new Object[objs.length];
for (int i = 0; i < objs.length; i ++) {
dests[i] = realize(objs[i], types[i]);
}
return dests;
} public static Object[] realize(Object[] objs, Class<?>[] types, Type[] gtypes) {
if (objs.length != types.length
|| objs.length != gtypes.length)
throw new IllegalArgumentException("args.length != types.length");
Object[] dests = new Object[objs.length];
for (int i = 0; i < objs.length; i ++) {
dests[i] = realize(objs[i], types[i], gtypes[i]);
}
return dests;
} public static Object generalize(Object pojo) {
return generalize(pojo, new IdentityHashMap<Object, Object>());
} @SuppressWarnings("unchecked")
private static Object generalize(Object pojo, Map<Object, Object> history) {
if (pojo == null) {
return null;
} if (pojo instanceof Enum<?>) {
return ((Enum<?>)pojo).name();
}
if (pojo.getClass().isArray()
&& Enum.class.isAssignableFrom(
pojo.getClass().getComponentType())) {
int len = Array.getLength(pojo);
String[] values = new String[len];
for (int i = 0; i < len; i ++) {
values[i] = ((Enum<?>)Array.get(pojo, i)).name();
}
return values;
} if (ReflectUtils.isPrimitives(pojo.getClass())) {
return pojo;
} if (pojo instanceof Class) {
return ((Class)pojo).getName();
} Object o = history.get(pojo);
if(o != null){
return o;
}
history.put(pojo, pojo); if (pojo.getClass().isArray()) {
int len = Array.getLength(pojo);
Object[] dest = new Object[len];
history.put(pojo, dest);
for (int i = 0; i < len; i ++) {
Object obj = Array.get(pojo, i);
dest[i] = generalize(obj, history);
}
return dest;
}
if (pojo instanceof Collection<?>) {
Collection<Object> src = (Collection<Object>)pojo;
int len = src.size();
Collection<Object> dest = (pojo instanceof List<?>) ? new ArrayList<Object>(len) : new HashSet<Object>(len);
history.put(pojo, dest);
for (Object obj : src) {
dest.add(generalize(obj, history));
}
return dest;
}
if (pojo instanceof Map<?, ?>) {
Map<Object, Object> src = (Map<Object, Object>)pojo;
Map<Object, Object> dest= createMap(src);
history.put(pojo, dest);
for (Map.Entry<Object, Object> obj : src.entrySet()) {
dest.put(generalize(obj.getKey(), history), generalize(obj.getValue(), history));
}
return dest;
}
Map<String, Object> map = new HashMap<String, Object>();
history.put(pojo, map);
map.put("class", pojo.getClass().getName());
for (Method method : pojo.getClass().getMethods()) {
if (ReflectUtils.isBeanPropertyReadMethod(method)) {
try {
map.put(ReflectUtils.getPropertyNameFromBeanReadMethod(method),
generalize(method.invoke(pojo), history));
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
// public field
for(Field field : pojo.getClass().getFields()) {
if (ReflectUtils.isPublicInstanceField(field)) {
try {
Object fieldValue = field.get(pojo);
// public filed同时也有get/set方法,如果get/set存取的不是前面那个 public field 该如何处理
if (history.containsKey(pojo)) {
Object pojoGenerilizedValue = history.get(pojo);
if (pojoGenerilizedValue instanceof Map
&& ((Map)pojoGenerilizedValue).containsKey(field.getName())) {
continue;
}
}
if (fieldValue != null) {
map.put(field.getName(), generalize(fieldValue, history));
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
return map;
} public static Object realize(Object pojo, Class<?> type) {
return realize0(pojo, type, null , new IdentityHashMap<Object, Object>());
} public static Object realize(Object pojo, Class<?> type, Type genericType) {
return realize0(pojo, type, genericType, new IdentityHashMap<Object, Object>());
} private static class PojoInvocationHandler implements InvocationHandler { private Map<Object, Object> map; public PojoInvocationHandler(Map<Object, Object> map) {
this.map = map;
} @SuppressWarnings("unchecked")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(map, args);
}
String methodName = method.getName();
Object value = null;
if (methodName.length() > 3 && methodName.startsWith("get")) {
value = map.get(methodName.substring(3, 4).toLowerCase() + methodName.substring(4));
} else if (methodName.length() > 2 && methodName.startsWith("is")) {
value = map.get(methodName.substring(2, 3).toLowerCase() + methodName.substring(3));
} else {
value = map.get(methodName.substring(0, 1).toLowerCase() + methodName.substring(1));
}
if (value instanceof Map<?,?> && ! Map.class.isAssignableFrom(method.getReturnType())) {
value = realize0((Map<String, Object>) value, method.getReturnType(), null, new IdentityHashMap<Object, Object>());
}
return value;
}
} @SuppressWarnings("unchecked")
private static Collection<Object> createCollection(Class<?> type, int len) {
if (type.isAssignableFrom(ArrayList.class)) {
return new ArrayList<Object>(len);
}
if (type.isAssignableFrom(HashSet.class)) {
return new HashSet<Object>(len);
}
if (! type.isInterface() && ! Modifier.isAbstract(type.getModifiers())) {
try {
return (Collection<Object>) type.newInstance();
} catch (Exception e) {
// ignore
}
}
return new ArrayList<Object>();
} private static Map createMap(Map src) {
Class<? extends Map> cl = src.getClass();
Map result = null;
if (HashMap.class == cl) {
result = new HashMap();
} else if (Hashtable.class == cl) {
result = new Hashtable();
} else if (IdentityHashMap.class == cl) {
result = new IdentityHashMap();
} else if (LinkedHashMap.class == cl) {
result = new LinkedHashMap();
} else if (Properties.class == cl) {
result = new Properties();
} else if (TreeMap.class == cl) {
result = new TreeMap();
} else if (WeakHashMap.class == cl) {
return new WeakHashMap();
} else if (ConcurrentHashMap.class == cl) {
result = new ConcurrentHashMap();
} else if (ConcurrentSkipListMap.class == cl) {
result = new ConcurrentSkipListMap();
} else {
try {
result = cl.newInstance();
} catch (Exception e) { /* ignore */ } if (result == null) {
try {
Constructor<?> constructor = cl.getConstructor(Map.class);
result = (Map)constructor.newInstance(Collections.EMPTY_MAP);
} catch (Exception e) { /* ignore */ }
}
} if (result == null) {
result = new HashMap<Object, Object>();
} return result;
} @SuppressWarnings({ "unchecked", "rawtypes" })
private static Object realize0(Object pojo, Class<?> type, Type genericType, final Map<Object, Object> history) {
if (pojo == null) {
return null;
} if (type != null && type.isEnum()
&& pojo.getClass() == String.class) {
return Enum.valueOf((Class<Enum>)type, (String)pojo);
} if (ReflectUtils.isPrimitives(pojo.getClass())
&& ! (type != null && type.isArray()
&& type.getComponentType().isEnum()
&& pojo.getClass() == String[].class)) {
return CompatibleTypeUtils.compatibleTypeConvert(pojo, type);
} Object o = history.get(pojo); if(o != null){
return o;
} history.put(pojo, pojo); if (pojo.getClass().isArray()) {
if (Collection.class.isAssignableFrom(type)) {
Class<?> ctype = pojo.getClass().getComponentType();
int len = Array.getLength(pojo);
Collection dest = createCollection(type, len);
history.put(pojo, dest);
for (int i = 0; i < len; i ++) {
Object obj = Array.get(pojo, i);
Object value = realize0(obj, ctype, null, history);
dest.add(value);
}
return dest;
} else {
Class<?> ctype = (type != null && type.isArray() ? type.getComponentType() : pojo.getClass().getComponentType());
int len = Array.getLength(pojo);
Object dest = Array.newInstance(ctype, len);
history.put(pojo, dest);
for (int i = 0; i < len; i ++) {
Object obj = Array.get(pojo, i);
Object value = realize0(obj, ctype, null, history);
Array.set(dest, i, value);
}
return dest;
}
} if (pojo instanceof Collection<?>) {
if (type.isArray()) {
Class<?> ctype = type.getComponentType();
Collection<Object> src = (Collection<Object>)pojo;
int len = src.size();
Object dest = Array.newInstance(ctype, len);
history.put(pojo, dest);
int i = 0;
for (Object obj : src) {
Object value = realize0(obj, ctype, null, history);
Array.set(dest, i, value);
i ++;
}
return dest;
} else {
Collection<Object> src = (Collection<Object>)pojo;
int len = src.size();
Collection<Object> dest = createCollection(type, len);
history.put(pojo, dest);
for (Object obj : src) {
Type keyType = getGenericClassByIndex(genericType, 0);
Class<?> keyClazz = obj.getClass() ;
if ( keyType instanceof Class){
keyClazz = (Class<?>)keyType;
}
Object value = realize0(obj, keyClazz, keyType, history);
dest.add(value);
}
return dest;
}
} if (pojo instanceof Map<?, ?> && type != null) {
Object className = ((Map<Object, Object>)pojo).get("class");
if (className instanceof String) {
try {
type = ClassHelper.forName((String)className);
} catch (ClassNotFoundException e) {
// ignore
}
}
Map<Object, Object> map ;
// 返回值类型不是方法签名类型的子集 并且 不是接口类型
if (!type.isInterface() && !type.isAssignableFrom(pojo.getClass())) {
try {
map = (Map<Object, Object>) type.newInstance();
Map<Object, Object> mapPojo = (Map<Object, Object>) pojo;
map.putAll(mapPojo);
map.remove("class");
} catch (Exception e) {
//ignore error
map = (Map<Object, Object>)pojo;
}
}else {
map = (Map<Object, Object>)pojo;
} if (Map.class.isAssignableFrom(type) || type == Object.class) {
final Map<Object, Object> result = createMap(map);
history.put(pojo, result);
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Type keyType = getGenericClassByIndex(genericType, 0);
Type valueType = getGenericClassByIndex(genericType, 1);
Class<?> keyClazz;
if ( keyType instanceof Class){
keyClazz = (Class<?>)keyType;
} else {
keyClazz = entry.getKey() == null ? null : entry.getKey().getClass();
}
Class<?> valueClazz;
if ( valueType instanceof Class){
valueClazz = (Class<?>)valueType;
} else {
valueClazz = entry.getValue() == null ? null : entry.getValue().getClass() ;
} Object key = keyClazz == null ? entry.getKey() : realize0(entry.getKey(), keyClazz, keyType, history);
Object value = valueClazz == null ? entry.getValue() : realize0(entry.getValue(), valueClazz, valueType, history);
result.put(key, value);
}
return result;
} else if (type.isInterface()) {
Object dest = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{type}, new PojoInvocationHandler(map));
history.put(pojo, dest);
return dest;
} else {
Object dest = newInstance(type);
history.put(pojo, dest);
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object key = entry.getKey();
if (key instanceof String) {
String name = (String) key;
Object value = entry.getValue();
if (value != null) {
Method method = getSetterMethod(dest.getClass(), name, value.getClass());
Field field = getField(dest.getClass(), name);
if (method != null) {
if (! method.isAccessible())
method.setAccessible(true);
Type ptype = method.getGenericParameterTypes()[0];
value = realize0(value, method.getParameterTypes()[0], ptype, history);
try {
method.invoke(dest, value);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to set pojo " + dest.getClass().getSimpleName() + " property " + name
+ " value " + value + "(" + value.getClass() + "), cause: " + e.getMessage(), e);
}
} else if (field != null) {
value = realize0(value, field.getType(), field.getGenericType(), history);
try {
field.set(dest, value);
} catch (IllegalAccessException e) {
throw new RuntimeException(
new StringBuilder(32)
.append("Failed to set filed ")
.append(name)
.append(" of pojo ")
.append(dest.getClass().getName())
.append( " : " )
.append(e.getMessage()).toString(),
e);
}
}
}
}
}
if (dest instanceof Throwable) {
Object message = map.get("message");
if (message instanceof String) {
try {
Field filed = Throwable.class.getDeclaredField("detailMessage");
if(! filed.isAccessible()) {
filed.setAccessible(true);
}
filed.set(dest, (String) message);
} catch (Exception e) {
}
}
}
return dest;
}
}
return pojo;
} /**
* 获取范型的类型
* @param genericType
* @param index
* @return List<Person> 返回Person.class ,Map<String,Person> index=0 返回String.class index=1 返回Person.class
*/
private static Type getGenericClassByIndex(Type genericType, int index){
Type clazz = null ;
//范型参数转换
if (genericType instanceof ParameterizedType){
ParameterizedType t = (ParameterizedType)genericType;
Type[] types = t.getActualTypeArguments();
clazz = types[index];
}
return clazz;
} private static Object newInstance(Class<?> cls) {
try {
return cls.newInstance();
} catch (Throwable t) {
try {
Constructor<?>[] constructors = cls.getConstructors();
if (constructors != null && constructors.length == 0) {
throw new RuntimeException("Illegal constructor: " + cls.getName());
}
Constructor<?> constructor = constructors[0];
if (constructor.getParameterTypes().length > 0) {
for (Constructor<?> c : constructors) {
if (c.getParameterTypes().length <
constructor.getParameterTypes().length) {
constructor = c;
if (constructor.getParameterTypes().length == 0) {
break;
}
}
}
}
return constructor.newInstance(new Object[constructor.getParameterTypes().length]);
} catch (InstantiationException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
} private static Method getSetterMethod(Class<?> cls, String property, Class<?> valueCls) {
String name = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
Method method = NAME_METHODS_CACHE.get(cls.getName() + "." + name + "(" +valueCls.getName() + ")");
if(method == null){
try {
method = cls.getMethod(name, valueCls);
} catch (NoSuchMethodException e) {
for (Method m : cls.getMethods()) {
if (ReflectUtils.isBeanPropertyWriteMethod(m)
&& m.getName().equals(name)) {
method = m;
}
}
}
if(method != null){
NAME_METHODS_CACHE.put(cls.getName() + "." + name + "(" +valueCls.getName() + ")", method);
}
}
return method;
} private static Field getField(Class<?> cls, String fieldName) {
Field result = null;
if (CLASS_FIELD_CACHE.containsKey(cls)
&& CLASS_FIELD_CACHE.get(cls).containsKey(fieldName)) {
return CLASS_FIELD_CACHE.get(cls).get(fieldName);
}
try {
result = cls.getField(fieldName);
} catch (NoSuchFieldException e) {
for(Field field : cls.getFields()) {
if (fieldName.equals(field.getName())
&& ReflectUtils.isPublicInstanceField(field)) {
result = field;
break;
}
}
}
if (result != null) {
ConcurrentMap<String, Field> fields = CLASS_FIELD_CACHE.get(cls);
if (fields == null) {
fields = new ConcurrentHashMap<String, Field>();
CLASS_FIELD_CACHE.putIfAbsent(cls, fields);
}
fields = CLASS_FIELD_CACHE.get(cls);
fields.putIfAbsent(fieldName, result);
}
return result;
} public static boolean isPojo(Class<?> cls) {
return ! ReflectUtils.isPrimitives(cls)
&& ! Collection.class.isAssignableFrom(cls)
&& ! Map.class.isAssignableFrom(cls);
} }

Dubbo 泛化调用的参数解析问题及一个强大的参数解析工具 PojoUtils的更多相关文章

  1. dubbo泛化调用 小demo

    前两天刚好有个同事来问是否用过 dubbo泛化 调用,不需要通过指定配置.第一次听到的时候,还是有点懵,但觉得有意思,可以学点东西. 立马百度了,找了demo,这篇比较容易上手(http://www. ...

  2. 一个强大的json解析工具类

    该工具类利用递归原理,能够将任意结构的json字符串进行解析.当然,如果需要解析为对应的实体对象时,就不能用了 package com.wot.cloudsensing.carrotfarm.util ...

  3. dubbo接口泛化调用例子

    @ApiOperation(value = "dubbo泛化调用工具接口") public Result dubboApiTool( @ApiParam(value = " ...

  4. Tkinter Button按钮组件如何调用一个可以传入参数的函数

    这里我们要使用python的lambda函数,lambda是创建一个匿名函数,冒号前十传入参数,后面是一个处理传入参数的单行表达式. 调用lambda函数返回表达式的结果. 首先让我们创建一个函数fu ...

  5. Dubbo高级特性实践-泛化调用

    引言 当后端Java服务用Dubbo协议作为RPC方案的基础,但部分消费方是前端Restful的PHP服务,不能直接调用,于是在中间架设了Router服务提供统一的基于HTTP的后端调用入口. 而Ro ...

  6. Dubbo基本特性之泛化调用

    Dubbo 是支持泛化调用的,什么是泛化调用呢?泛化调用的好处是什么呢,泛化调用说白一点就是服务消费者并没有服务的接口. 在<Dubbo入门-搭建一个最简单的Demo框架>一文中,我们已完 ...

  7. SOFARPC —— Generic Service (泛化调用) 解析

    今晚心情无比激动,多云转晴!原因在于弄懂些 Generic Service 实现原理,很有成就感. 各位看官莫笑,今晚,小小的收获,也是非常满足的.下面进入正题! 一.前言 普遍RPC在客户端需要提供 ...

  8. Dubbo学习笔记4:服务消费端泛化调用与异步调用

    本文借用dubbo.learn的Dubbo API方式来解释原理. 服务消费端泛化调用 前面我们讲解到,基于Spring和基于Dubbo API方式搭建简单的分布式系统时,服务消费端引入了一个SDK二 ...

  9. Dubbo 高级特性实践-泛化调用

    引言 当后端Java服务用Dubbo协议作为RPC方案的基础,但部分消费方是前端Restful的PHP服务,不能直接调用,于是在中间架设了Router服务提供统一的基于HTTP的后端调用入口. 而Ro ...

随机推荐

  1. Echarts跟随容器自适应大小问题

    窗口大小改变市echarts图表常常会溢出,这时候会很难看,于是查看文档和百度下后,有如下解决方案: var myChart = echarts.init(document.getElementByI ...

  2. [LeetCode] Card Flipping Game 翻卡片游戏

    On a table are N cards, with a positive integer printed on the front and back of each card (possibly ...

  3. Educational Codeforces Round 6

    620A - Professor GukiZ's Robot    20171122 \(ans=max(\left | x2-x1 \right |,\left | y2-y1 \right |)\ ...

  4. popwindow+动画

    布局: main: <Button android:id="@+id/btn" android:layout_width="match_parent" a ...

  5. Bear + Reminders 是完美的Thing 3 的替代品

    如今同类功能的APP在AppStore上呈现泛滥之势,尤其是时间管理.任务管理之类的APP.其中比较出名的就有“Things 3”这款APP,这是一款多年不更新,一更新就获奖的APP.目前在AppSt ...

  6. mvc模式的理解

    一开始总是觉得dao层和service层没有区别,甚至觉得service层根本就是多余的,service层就是把dao层的内容调用了一下,然后重写了一次,后来才理解到,dao层是数据链路层,是与数据库 ...

  7. Setup Factory使用

  8. LeetCode 122 Best Time to Buy and Sell Stock II 解题报告

    题目要求 Say you have an array for which the ith element is the price of a given stock on day i. Design ...

  9. MongoDB Schema Design

    Normalization: ...... Relationships: One-to-One relationship: 1-to-many: many-to-many:

  10. Linux netfilter 学习笔记

    https://blog.csdn.net/lickylin/article/details/33321905