spring 4 升級踩雷指南
前言
最近,一直在為公司老專案做核心庫升級工作。本來只是想升級一下 JDK8 ,卻因為相容性問題而不得不升級一些其他的庫,而其他庫本身依賴的一些庫可能也要同步升級。這是一系列連鎖問題,你很難一一識別,往往只有在編譯時、執行時才能發現問題。
總之,這是個費勁的活啊。
本文小結一下升級 Spring4 的連鎖問題。
為什麼升級 spring4
升級 Spring4 的原因是:Spring 4 以前的版本不相容 JDK8。當你的專案同時使用 Spring3 和 JDK8,如果程式碼中有使用 JDK8 位元組碼或 Lambada 表示式,那麼會出問題。
也許你會問,為什麼不使用最新的 Spring 5 呢?因為作為企業軟體,一般更傾向使用穩定的版本(bug 少),而不是最新的版本,尤其是一些核心庫。
更多細節可以參考:
https://spring.io/blog/2013/05/21/spring-framework-4-0-m1-3-2-3-available/
spring 4 重要新特性
Spring 4 相比 Spring 3,引入許多新特性,這裡列舉幾條較為重要的:
- 支援
JDK8
(這個是最主要的)。 Groovy Bean Definition DSL
風格配置。- 支援 WebSocket、SockJS、STOMP 訊息
- 移除 Deprecated 包和方法
- 一些功能加強,如:核心容器、Web、Test 等等,不一一列舉。
更多 Spring 4 新特性可以參考:
http://jinnianshilongnian.iteye.com/blog/1995111
升級 spring 4 步驟
瞭解了前面內容,我們知道了升級 Spring 4 帶來的好處。現在開始真刀真槍的升級了。
不要以為升級一下 Spring 4,僅僅是改一下版本號,那麼簡單,細節處多著呢。
下面,結合我在公司專案升級 Spring4 時遇到的一系列坑,希望能幫助各位少走彎路。
注
下文內容基於假設你的專案是用 maven 管理這一前提。如果不滿足這一前提,那麼這篇文章對你沒什麼太大幫助。
修改 spring 版本
第一步,當然是修改 pom.xml 中的 spring 版本。
3.x.x.RELEASE
> 4.x.x.RELEASE
例項:升級 spring-core
其它 spring 庫的升級也如此:
<properties>
<spring.version>4.3.13.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
修改 spring xml 檔案的 xsd
用過 spring 的都知道,spring 通常依賴於大量的 xml 配置。
spring 的 xml 解析器在解析 xml 時,需要讀取 xml schema,schema 定義了 xml 的名稱空間。它的好處在於可以避免命名衝突,有點像 Java 中的 package。
例項:一個 spring xml 的 schema
<?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:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
說明
xmlns="http://www.springframework.org/schema/beans"
宣告 xml 檔案預設的名稱空間,表示未使用其他名稱空間的所有標籤的預設名稱空間。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
宣告XML Schema 例項,宣告後就可以使用 schemaLocation 屬性了。
xmlns:mvc="http://www.springframework.org/schema/mvc"
宣告字首為 mvc 的名稱空間,後面的 URL 用於標示名稱空間的地址不會被解析器用於查詢資訊。其惟一的作用是賦予名稱空間一個惟一的名稱。當名稱空間被定義在元素的開始標籤中時,所有帶有相同字首的子元素都會與同一個名稱空間相關聯。 其它的類似xmlns:context
、xmlns:jdbc
等等同樣如此。xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd ..."
這個從命名可以看出個大概,指定 schema 位置這個屬性必須結合名稱空間使用。這個屬性有兩個值,第一個值表示需要使用的名稱空間。第二個值表示供名稱空間使用的 xml schema 的位置。
上面示例中的 xsd 版本是 3.1.xsd
,表示 spring 的 xml 解析器會將其視為 3.1 版本的 xml 檔案來處理。
現在,我們使用了 Spring 4,3.1.xsd
版本顯然就不正確了,我們可以根據自己引入的 Spring 4 的子版本號將其改為 4.x.xsd
。
但是,還有一種更好的做法:把這個指定 xsd 版本的關鍵字幹掉,類似這樣:http://www.springframework.org/schema/tx/spring-tx.xsd
。
這麼做的原因如下:
- Spring 預設在啟動時要載入 xsd 檔案來驗證 xml 檔案。
- 如果沒有提供
schemaLocation
,那麼 spring 的 xml 解析器會從 namespace 的 uri 里載入 xsd 檔案。 schemaLocation
提供了一個 xml namespace 到對應的 xsd 檔案的一個對映。- 如果不指定 spring xsd 的版本號,spring 取的就是當前本地 jar 裡的 xsd 檔案,減少了各種風險(比如 xsd 與實際 spring jar 版本不一致)。
更多詳細內容可以參考這篇文章:為什麼在Spring的配置裡,最好不要配置xsd檔案的版本號
修改 spring xml 檔案
spring 4 對 xml 做了一些改動。這裡說一個最常用的改動:
ref local
spring 不再支援 ref
元素的 local
屬性,如果你的專案中使用了,需要改為 bean
。
shi
spring 4 以前:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
spring 4 以後:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
如果不改啟動會報錯:
Caused by: org.xml.sax.SAXParseException: cvc-complex-type.3.2.2: Attribute 'local' is not allowed to appear in element 'ref'.
當然,可能還有一些其他配置改動,這個只能說兵來將擋水來土掩,遇到了再去查官方文件吧。
加入 spring support
spring 3 中很多的擴充套件內容不需要引入support 。但是 spring 4 中分離的更徹底了,如果不分離,會有很多ClassNotFound
。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
更換 spring-mvc jackson
spring mvc 中如果返回結果為 json 需要依賴 jackson 的jar包,但是他升級到了2, 以前是 codehaus.jackson
,現在換成了 fasterxml.jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.0</version>
</dependency>
同時修改spring mvc的配置檔案:
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</list>
</property>
</bean>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
解決 ibatis 相容問題
問題
如果你的專案中使用了 ibatis (mybatis 的前身)這個 orm 框架,當 spring3 升級 spring4 後,會出現相容性問題,編譯都不能通過。
這是因為 Spring4 官方已經不再支援 ibatis。
解決方案
新增相容性 jar 包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-2-spring</artifactId>
<version>1.0.1</version>
</dependency>
更多內容可參考:https://stackoverflow.com/questions/32353286/no-support-for-ibatis-in-spring4-2-0
升級 Dubbo
我們的專案中使用了 soa 框架 Dubbo 。由於 Dubbo 是老版本的,具體來說是(2013年的 2.4.10),而老版本中使用的 spirng 版本為2.x,有相容性問題。
Dubbo 專案從今年開始恢復維護了,首先把一些落後的庫升級到較新版本,比如 jdk8,spring4 等,並修復了一些 bug。所以,我們可以通過升級一下 Dubbo 版本來解決問題。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.8</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
</exclusions>
</dependency>
升級 Jedis
升級 Dubbo 為當前最新的 2.5.8 版本後,執行時報錯:
- JedisPoolConfig 配置錯誤
Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
由於專案中使用了 redis,版本為 2.0.0 ,這個問題是由於 jedis 需要升級:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
jedis 2.4.1 以上版本的 JedisPoolConfig
已經沒有了maxActive
和 maxWait
屬性。
修改方法如下:
maxActive > maxTotal
maxWait > maxWaitMillis
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="200" />
<property name="maxIdle" value="10" />
<property name="maxWaitMillis" value="1000" />
<property name="testOnBorrow" value="true" />
</bean>
JedisPool 配置錯誤
InvalidURIException: Cannot open Redis connection due invalid URI
原來的配置如下:
<bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="destroy" depends-on="jedisPoolConfig">
<constructor-arg ref="jedisPoolConfig" />
<constructor-arg type="java.lang.String" value="${redis.host}" />
<constructor-arg type="int" value="${redis.port}" />
</bean>
檢視原始碼可以發現,初始化 JedisPool 時未指定結構方法引數的型別,導致 host 字串值被視為 URI 型別,當然型別不匹配。
解決方法是修改上面的host 配置,為:<constructor-arg type="java.lang.String" value="${redis.host}" />
至此,spring 4 升級結束。後面如果遇到其他升級問題再補充。
資料
- https://spring.io/blog/2013/05/21/spring-framework-4-0-m1-3-2-3-available/
- https://docs.spring.io/spring/docs/4.3.14.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#spring-whats-new
- Spring 3.x 升級到Spring 4.x 注意事項和步驟,錯誤解決方法
- http://jinnianshilongnian.iteye.com/blog/1995111
- 為什麼在Spring的配置裡,最好不要配置xsd檔案的版本號
- https://stackoverflow.com/questions/32353286/no-support-for-ibatis-in-spring4-2-0