前言
升级的应用为tomcat端应用。在升级过程中必须保证3者全部升级。
升级前版本 | 升级后版本 | |
spring | 2.5.6.SEC03 | 5.1.7.RELEASE |
jdk | 1.7 | 1.8 |
dubbo | 2.5.3 | 2.7.2 |
为何三者都要升级?
2.5.6版本spring-context并不支持jdk1.8,2.5.3版本的dubbo强依赖2.5.6版本的spring。 所以,如果你的项目也是这三个版本组合,要升级任一时,都必须全部升级。
此处要注意,dubbo要选择2.7.2版本,2.7.0是有Bug的,当然如果你的系统中没有hessian协议的服务,也就可以无视这个Bug,不过还是建议2.7.2,毕竟相比2.7.0也删除调整了一些类。(这一点我很想吐槽,只是更新了2个小版本,甚至可以说是补丁也不为过,却涉及删除类。。。这操作感觉不符合阿里这种大厂该有的。删除类这种调整我认为起码得变更版本号中间的数字。)
建议: 请先升级消费端,再升级服务端
升级jdk
首先,先升级jdk,只需要更改pom.xml中的jdk版本即可。没有什么难度,这里就不展示代码了。
当然要对应的改动打包的jdk版本,还有运行的jdk版本。
升级Spring
引入最新版本的Spring jar包,如果有需要排除的包记得排除。如果你用的是idea,这里推荐maven helper插件可以很直观的看到jar包依赖关系。
<properties>
<spring.version>5.1.7.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
调整spring xml配置
如果应用中的spring xml配置xsi:schemaLocation的dtd,xsd有设置版本,就要更改了,最好是删除版本号。例如:http://www.springframework.org/schema/beans/spring-beans.xsd
升级dubbo
引入新版本的dubbo,记得排掉旧版本的dubbo依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
调整xml配置
更改spring xml配置文件中xsi:schemaLocation引入的xsd,dtd等版本。(最好是删除后缀版本。如http://www.springframework.org/schema/beans/spring-beans.xsd)
全局搜索code.alibabatech.com
更改为dubbo.apache.org
调整Spring扫描xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="com.howbuy.trade.huodong.controller"/>
<dubbo:annotation package="com.howbuy.trade.huodong.controller"></dubbo:annotation>
</beans>
调整配置
升级后,推荐使用注解方式,虽然xml也支持,不过dubbo明显对xml没有做很好的再扩展,很多新的配置在注解中可以很容易找到配置点,但在xml中却很难配置。比如hessian的方法重载配置。
调整配置(消费端)
以下是注解的配置方式,无论是controller还是service引入依赖的服务用@Reference注解来进行注入。
消费端无需关心服务本身是否是dubbo或者hessian,所以可以忽略protocol
@Reference(registry = "wechatRegistry", interfaceClass = WechatQueryService.class)
private WechatQueryService wechatQueryService;
@Reference(registry = "member", interfaceClass = MemberCustInfoService.class)
private MemberCustInfoService memberCustInfoService;
调整配置(服务端)
通过Service注解来实现服务的注册,注意这里的Service是org.apache.dubbo下的,并不是spring下的。
@Service
public class OperatorServiceImpl extends BaseServiceImpl implements OperatorService {
}
应用中可能会出现同名不同包的服务。
可以使用如下配置方式。(可不可以直接@Service("cms.operatorService")
呢? 我主要在2.7.0上测试了,并且发现只能这样写,至于2.7.2我并没有再去测试。)
@org.springframework.stereotype.Service("cms.operatorService")
@Service
public class OperatorServiceImpl extends BaseServiceImpl implements OperatorService {
}
服务端hessian配置
@Service(protocol = "hessian", timeout = 3000,parameters = {Constants.HESSIAN_OVERLOAD_METHOD_KEY,"true"})
public class FileUploadServiceImpl implements IFileUploadService {
}
注意parameters参数,其中HESSIAN_OVERLOAD_METHOD_KEY
配置就是为了解决hessian方法重载的问题,这也是2.7.0的bug点。
(这里我又要吐槽,在2.5.3时解决hessian的方法重载问题我是通过更改2.5.3的源码包实现的。在我升级时只有2.7.0版本,在我将消费端升级到2.7.0后,查看到源码有Constants.HESSIAN_OVERLOAD_METHOD_KEY
的解析,相应的我认为2.7.0已经解决了hessian的方法重载问题。在之后升级服务端后发现,parameters会出现array转map异常。这里分析源码后发现,dubbo包下是有arrayToMap的解析器的,但是却并没有使用。这就会造成parameters参数根本没有任何用途。还好这时候已经有了2.7.2,通过分析2.7.2发现这个问题已经解决。)
高低dubbo版本兼容问题
升级过程中必然会出现部分应用是高版本dubbo,部分应用是低版本dubbo。
如果协议为dubbo在测试下来没发现兼容问题,还是在hessian处有问题。
拉取低版本dubbo的源码,找到ServiceConfig
类,大约在285行左右doExportUrlsFor1Protocol方法中有个map(变量名就叫map),在方法内会看到会向map中填入很多参数,增加:
if(name.equals("hessian")){
map.put("hessian.overload.method","true");
}
重新打包,并发布到自己maven库中,调整自己的dubbo应用依赖自己的dubbo即可。
补充(20190729):
在升级时,hessian协议会出现很多问题,建议查看后续我对hessian问题的补充。
升级过程中可能出现的问题汇总
1. RpcResult等找不到
引入org.apache.dubbo包下的RpcResult即可(毕竟2.7.0已经是spring了)
2.org/eclipse/jetty/util/log/Logger不存在?
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
3. javax.el.xxxx报错?
这个问题是因为tomcat版本的问题,请尽量使用tomcat8。如果不方便升级,可以将tomcat->lib下的el-api.jar更改为tomcat8中的jar
4. hessian问题
在升级过程中,上述的hessian.overload.method
是为了解决hessian服务存在重载方法的兼容问题。
如果hessian接口的形参为一个对象,比如dto,如果子类的对象存在与父类对象同名的成员变量,会造成该变量值丢失。 解决方法: 1). 先拉去hessian的源码到本地。2). 更改HessianProxyFactory
类getSerializerFactory()
方法中new SerializerFactory
为new BeanSerializerFactory
。
/**
* Gets the serializer factory.
*/
public SerializerFactory getSerializerFactory()
{
if (_serializerFactory == null)
// _serializerFactory = new SerializerFactory(_loader);
_serializerFactory = new BeanSerializerFactory();
return _serializerFactory;
}
3). 重新定义hessian的版本为自定版本,并发布。
4). 更改消费端的hessian版本为自定版本。(这里可以不更新服务端的,当然如果对服务端有上线操作,还是建议升级一下)。
一下是我自己的测试结果,供参考:
4.0.7-fixed是原hessian版本,4.0.7.1是在4.0.7-fixed基础上修复了序列化问题的自定版本。
版本访问 | 结果 |
4.0.7.1 -> 4.0.7.1 | √ |
4.0.7.1 -> 4.0.7-fixed | √ |
4.0.7-fixed -> 4.0.7.1 | × |
4.0.7-fixed -> 4.0.7-fixed | × |
补充(20190809):
将hessian的序列化方式改为BeanSerializerFactory
后,虽然可以解决父子类包含相同成员变量名时参数丢失的问题,但是会出现返回值为List
等反序列化失败的问题。
我尝试使用JavaSerializer、UnsafeSerializer后,这两个序列化虽然能保证所有的返回值都能反序列化,但是却无法保证父子类相同成员变量名的问题。
这就好像鱼和熊掌不可兼得。 最后我在Hessian2Input
中查到相异之处。在readObject()
中BeanSerializerFactory会进入case 119
,其它序列化方式会进入case 111
。而问题就出现在case 119
处this.findSerializerFactory().getListDeserializer(type,(Class)null)
。
那如果更改此处源码,直接用case 111
来解析会如何?是不是就可以解决鱼和熊掌的问题?