好比咱们有个dubbo的服务接口是这样定义的,java
public interface UserService { String sayHello1(String name); }
服务实现示例以下:web
public class UserServiceImpl implements UserService { public String sayHello1(String s) { return "hello1 " + s + "!"; }
而后咱们中间服务进行升级,须要增长一个入参,把接口改为了以下的方式:spring
public interface UserService { String sayHello1(String name, int age); }
服务实现改为了:segmentfault
public class UserServiceImpl implements UserService { public String sayHello1(String s, int age) { return "hello1 " + s + age + "!"; }
服务上线后,调用方确定会报错,由于整个接口声明都变了,至关于两个彻底不一样的接口。api
那如何解决呢?其实很简单。服务接口的参数类型最好是封装类,增长参数的话只是在这个类增长一个字段。示例以下:app
public interface UserService { String sayHello1(String name); String sayHello2(SayWorldRequest request);
封装类的定义:dom
public class SayWorldRequest implements Serializable { private static final long serialVersionUID = 1L; String name; int age; ...
当一个接口实现,出现不兼容升级时颇有用。能够用版本号过渡,版本号不一样的服务相互间不引用。ide
仍是经过示例来讲明:.net
假设服务端的要对接口sayHello1重写,和原来的方法不兼容,咱们重写写个实现类,而后实现sayHello1方法,code
public class UserServiceImpl2 implements UserService { public String sayHello1(String s) { return "hello1 new" + s + "!"; }
而后暴露服务的时候指定不一样的版本,
<bean id="userService" class="com.dubbo.provider.UserServiceImpl"/> <bean id="userService2" class="com.dubbo.provider.UserServiceImpl2"/> <dubbo:service interface="com.dubbo.api.UserService" ref="userService" version="1.0"/> <dubbo:service interface="com.dubbo.api.UserService" ref="userService2" version="1.1"/>
客户端调用的时候,能够经过指定版本号调用不一样的服务,
<!-- 引用服务配置 --> <dubbo:reference id="userServiceClient" interface="com.dubbo.api.UserService" cluster="failfast" check="false" version="1.1"/>
聪明如你可能想到了,利用版本号的机制能够实现灰度发布,举个例子:
<!-- 机器A提供1.0.0版本服务 --> <dubbo:service interface="com.dubbo.api.UserService" version="1.0.0" /> <!-- 机器B提供2.0.0版本服务 --> <dubbo:service interface="com.dubbo.api.UserService" version="2.0.0" /> <!-- 机器C消费1.0.0版本服务 --> <dubbo:reference id="userService" interface="com.dubbo.api.UserService" version="1.0.0" /> <!-- 机器D消费2.0.0版本服务 --> <dubbo:reference id="userService" interface="com.dubbo.api.UserService" version="2.0.0" />
《阿里巴巴java开发手册》有这么一条规定:
【强制】二方库里能够定义枚举类型,参数可使用枚举类型,可是接口返回值不容许使用枚
举类型或者包含枚举类型的 POJO 对象。
为何有这样的规定呢?
先给你看个例子:
接口定义,
public interface UserService { SayWorldResponse sayHello3(SayWorldRequest request); }
响应类定义,
public class SayWorldResponse implements Serializable { private static final long serialVersionUID = 1L; String name; int age; EnumColor enumColor; //省略其它部分
枚举定义,
public enum EnumColor { RED("red", 1), GREEN("green", 2), BLACK("black", 3), YELLOW("yellow", 4); //省略其它部分
服务实现,
public class UserServiceImpl implements UserService { //省略其它部分 public SayWorldResponse sayHello3(SayWorldRequest request) { SayWorldResponse response = new SayWorldResponse(); response.setAge(100); response.setName("response"); response.setEnumColor(EnumColor.YELLOW); return response; } }
而后系统上线,客户端正常调用,一切风平浪静。
可是软件总会升级,过了一段时间,咱们须要在枚举里新增一个类型,而且服务端在某些场景下会使用这个字段返回给客户端。代码以下:
public enum EnumColor { RED("red", 1), GREEN("green", 2), BLACK("black", 3), YELLOW("yellow", 4), BLACK("black", 5); //省略其它部分
服务实现,
public class UserServiceImpl implements UserService { //省略其它部分 public SayWorldResponse sayHello3(SayWorldRequest request) { SayWorldResponse response = new SayWorldResponse(); response.setAge(100); response.setName("response"); if (条件成立) { response.setEnumColor(EnumColor.BLACK); } else { response.setEnumColor(EnumColor.YELLOW); } return response; } }
这个时候就至关于埋下了一颗雷,服务端上线后,当客户端调用sayHello3方法触发条件返回 EnunColor.BLACK时,客户端就会报以下的错误:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.alibaba.dubbo.rpc.RpcException: Failfast invoke providers dubbo://10.204.246.56:28511/com.dubbo.api.UserService2?anyhost=true&application=dubbo-consumer&check=false&cluster=failfast&default.check=false&default.cluster=failfast&dubbo=2.5.3&interface=com.dubbo.api.UserService&methods=sayHello1,sayHello2,sayHello3&pid=18056&revision=1.1-SNAPSHOT&side=consumer×tamp=1576573801502&version=1.1 RandomLoadBalance select from all providers [com.alibaba.dubbo.registry.integration.RegistryDirectory$InvokerDelegete@23c21e07] for service com.dubbo.api.UserService method sayHello3 on consumer 10.204.246.56 use dubbo version 2.5.3, but no luck to perform the invocation. Last error is: Failed to invoke remote method: sayHello3, provider: dubbo://10.204.246.56:28511/com.dubbo.api.UserService2?anyhost=true&application=dubbo-consumer&check=false&cluster=failfast&default.check=false&default.cluster=failfast&dubbo=2.5.3&interface=com.dubbo.api.UserService&methods=sayHello1,sayHello2,sayHello3&pid=18056&revision=1.1-SNAPSHOT&side=consumer×tamp=1576573801502&version=1.1, cause: com.alibaba.com.caucho.hessian.io.HessianFieldException: com.dubbo.api.SayWorldResponse.enumColor: java.lang.reflect.InvocationTargetException com.alibaba.com.caucho.hessian.io.HessianFieldException: com.dubbo.api.SayWorldResponse.enumColor: java.lang.reflect.InvocationTargetException at com.alibaba.com.caucho.hessian.io.JavaDeserializer.logDeserializeError(JavaDeserializer.java:671) at com.alibaba.com.caucho.hessian.io.JavaDeserializer$ObjectFieldDeserializer.deserialize(JavaDeserializer.java:400) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:233) at com.alibaba.com.caucho.hessian.io.JavaDeserializer.readObject(JavaDeserializer.java:157) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObjectInstance(Hessian2Input.java:2067) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1592) at com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject(Hessian2Input.java:1576) ...
很明显,这是一个RPC调用过程当中反序列的错误。为何会有这个错误呢?解释下:
在Java中,对Enum类型的序列化与其余对象类型的序列化有所不一样的,官方的说明是这样的:
Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.
The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是经过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不容许任何对这种序列化机制的定制的,所以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
因此,在远程方法调用过程当中,若是咱们发布的客户端接口返回值中使用了枚举类型,那么服务端在升级过程当中好比在接口的返回结果的枚举类型中添加了新的枚举值,那就会致使仍然在使用老的客户端的那些应用出现调用失败的状况。
那么有没有解决办法呢?固然,方法就是客户端和服务端都升级,都引用最新的API声明。不过每每实际的项目中很难保证这一点(不一样的团队维护),因此咱们仍是要尽可能避免RPC接口的返回值里面包含枚举定义。
关注公众号:犀牛饲养员的技术笔记
csdn博客: https://blog.csdn.net/pony_ma...