好比咱們有個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...