spring 4 升級踩雷指南

weixin_34015860發表於2017-12-15

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,引入許多新特性,這裡列舉幾條較為重要的:

  1. 支援 JDK8 (這個是最主要的)。
  2. Groovy Bean Definition DSL 風格配置。
  3. 支援 WebSocket、SockJS、STOMP 訊息
  4. 移除 Deprecated 包和方法
  5. 一些功能加強,如:核心容器、Web、Test 等等,不一一列舉。

更多 Spring 4 新特性可以參考:

https://docs.spring.io/spring/docs/4.3.14.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#spring-whats-new

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:contextxmlns: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 已經沒有了maxActivemaxWait 屬性。

修改方法如下:

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 升級結束。後面如果遇到其他升級問題再補充。

資料

相關文章