一個 Spring 項目,打成 jar 包之後運行,在有網絡的時候是正常的,但是一旦無網絡就會報錯,具體是怎麼回事呢?這篇文章就來記錄下這次問題排查經過。
背景介紹
一個圖形化的界面,帶本地數據庫,要求可以在無網絡環境下運行,我幫朋友用的 Java 寫的圖形化界面,雖然不是很美觀,但是勝在熟悉 Java。
項目使用的是 idea 的「Build Artifacts」打包,打包之後運行正常,界面和數據庫訪問都正常,最開始報過幾次錯,後來就沒出現了,也沒找到原因,就先那樣了。
後來發給別人了,完全打不開,跟之前的報錯一樣,看來必須要搞清楚報錯的原因了。
問題排查
報錯內容
首先貼一下報錯的信息:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException:
Line 8 in XML document from class path resource [spring.xml] is invalid;
nested exception is org.xml.sax.SAXParseException;
lineNumber: 8; columnNumber: 76;
cvc-elt.1: 找不到元素 'beans' 的聲明。
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
問題復現
因爲我之前也碰到過這個報錯,所以想着復現一下問題,方便排查。
我直接在虛擬機中運行,無論是覆蓋安裝還是卸載再次安裝都沒問題,我就想可能是環境問題導致的吧,就想着裝一個新的虛擬機試一下。
新系統裝完之後,一運行,果然崩潰了,報錯信息一樣。能復現問題,可以說爲解決問題開了個好頭。
問題排查
找到原因
通過關鍵字 cvc-elt.1: 找不到元素 'beans' 的聲明
開始搜索,搜索到的結果基本都是以下的內容。
配置文件頭部配置的 xsd 版本信息不正確,造成解析時出錯。spring 頭部 xsd 或 dtd 校驗文件的查找分兩步,第一先從本地 jar 包中找,如果找到則用本地jar包的進行校驗(可以在 spring-beans.jar 或 spring-context.jar 裏的 META-INF 下的 spring.schemas 文件中找到 xsd 文件位置的定義),如果沒有找到則進行第二步查找,它會嘗試從網絡中下載該文件然後校驗,如果系統斷網或下載不下來,則會拋出上述異常。
照着這個思路,朋友也提醒我,看下虛擬機的系統是否聯網了,我一看,果然沒聯網,趕緊聯網測試發現能正常運行,問題的原因找到了。
再次困惑
找到了問題原因,好像就馬上就能解決了,但事實並不是我想的那樣。
網上的解決辦法是:
將 applicationContext.xml 中 xsd 文件定義的版本改爲 spring jar 包中定義的 xsd 的版本,如果版本定義的太高在本地會無法找到,只能從網絡上下載。
但是,查看 spring-beans.jar
裏 META-INF
下的 spring.schemas
文件,發現無論版本號是多少,最後指向的都是一個。
截取了部分代碼如下:
http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
後來又搜到一篇文章「spring5之SAXParseException:cvc-elt.1: 找不到元素 “beans” 的聲明」,說 Spring 5
之後就不需要寫版本號了。
那跟我看到的情況是一樣的,確實各個版本號指向的都是同一個,看來不是版本號的問題,那到底是怎麼回事?
一波三折
既然 xsd
的版本沒錯,那問題出在哪裏呢?
繼續搜索之後,看到了這篇文章「系統啓動時,spring配置文件解析失敗,報”cvc-elt.1: 找不到元素 ‘beans’ 的聲明“異常」。
打包之後,spring.schemas
位於 jar 包的 META-INF
目錄下,但是 spring.schemas
裏並沒有 spring-beans.xsd
,看來問題是在這了,我仿照作者的方法,把上面提到的 spring-beans.jar
裏 META-INF
下的 spring.schemas
裏的關於 spring-beans.xsd
的部分添加到了壓縮包的 spring.schemas
裏。
重新打包之後仍然報錯,但是報的錯不是之前的錯了,是說我的主函數類找不到了,真的是讓人摸不着頭腦,情況類似啊,爲什麼我的不好使。
豁然開朗
還是上面的操作,我讓朋友試了一下,他測試之後居然好使!
我問他怎麼操作的,他說跟我的操作一樣,只不過把 https
部分的也加上了,我心想不應該啊,也沒引用 https
的 spring-beans.xsd
,不應該是這個問題吧。
我按照他的操作,在我電腦上重新打包,發現還是不好使;我又把他給我的文件替換到我打的包裏,還是不好使。真的是蒙了,操作是一樣的,爲什麼我的不好使。
後來朋友說讓我把修改好的包發給他,他看了之後發現我打的包大小不一樣,正常應該是 14MB,但是我的包是 500KB,我又重新操作了一遍,發現我把文件替換之後,整個 jar 包的大小變小了,原來是我的壓縮包軟件有問題。
朋友的電腦是 Windows,是直接用 WinRAR 打開 jar 包,然後把文件替換;我的是 Mac,用的 eZip 軟件打開 jar 包,然後替換文件。最後,我使用虛擬機按照朋友的操作測試後發現 ok 了。
誰能料到是壓縮軟件的問題呢?但是也不能全怪軟件,畢竟人家是解壓縮軟件,並不是專門處理 jar 文件的。
完美解決
雖然找到了問題原因,也有了解決辦法,但是解決的辦法卻不夠好,總不能每次打好包再手動替換文件吧?
我覺得既然我們能遇到的問題,肯定也有其他人遇到,我就使用關鍵字 spring.schemas 打包之後沒有把 beans 打進去
進行了搜索,發現了完美的解決方案:「spring打包到jar問題」。
問題的根本原因是,由於 spring 多個 jar 包都包含 spring.schemas
,「Build Artifacts」和「Maven」默認打包只會把第一次遇到的 schemas 文件打入 jar 包。
解決辦法是使用 maven shade
打包,具體的代碼如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>xxx.xxx.xxx.App</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.tooling</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
將代碼放在項目 pom.xml
的 <plugins>
節點裏,其中代碼裏的 <mainClass>
節點裏要寫主函數的完整目錄。
感受
問題解決了,從中也得到一些感受:
- 遇到問題還是得儘快解決,因爲問題是不會放過你的。
- Mac 下的壓縮軟件暫時沒有 Windows 下的好使。
- 網上的答案真的是千篇一律,都是一個人的答案被很多人複製,而且沒有講清楚問題。
- 搜到的千篇一律的答案,可能已經過時了,並不適合自己的情況。
- 自己遇到的問題,絕大部分都是別人遇到過的,是可以解決的,耐心搜索就會找到解決辦法。
歡迎訪問的個人博客:掘墓人的小鏟子