shade插件解决打包后无法加载spring xsd文件办法

有时候,需要将复杂的项目, 包括各种xml文件属性文件, 类, jar等等打包到一个可以执行的jar包中, 然后用java -jar  xxx.jar 来运行项目, 这样简单方便, 特别是在编写一些测试工具时,尤为重要。

但是经常发现打包后的项目无法启动, 其中一大类是您项目有问题, 但是这个相对好解决, 毕竟自己的项目可以在windows下的ide中做各种调试,测试都测试好了, 在打包一般程序问题的概率就低得多了。

但是另外一类问题就是spring 需要在加载的时候获取spring的xsd文件, 通常这些文件都是可以动态从网络下载的, 因此在网络畅通情况下, 还是不是问题的。

问题是, 当您的系统部署到服务器时, 可能会有问题, 通常我们的生产环境的内部服务器一般都是限制访问外部网络的, 因此这个时候, 您就发现无法加载spring的 xsd文件了, 因此您的项目或者工具也无法使用了。

在您本地开发环境偶尔也可能有这个问题。

通常解决的办法是采用maven的shade插件进行打包, 并且在打包时设置一下spring xsd文件的处理方式, 一般就可以解决。

另外采用maven打包中还有jar差距, 和另外一个插件打包, 这两个方式的解决就比较麻烦了, 也许新版的相关差距会好些。下面摘录了 相关的从网络上获取的资料。因为有资料了就不在重复写了,贴到这里。

-------------

问题的产生:有个Java项目(Jar文件),每半小时重启一次,对外提供服务。

突然收到报警,早上5点半重启服务时出错,服务无法正常启动。(故障通常都在你最不想发生的时候才发生, 突然断网, 网络流量突然大增等等)

查看启动日志,错误是 xml解析失败,无法找到xml元素的声明。

具体报错日志如下:

INFO: Loading XML bean definitions from class path resource [applicationContext-task.xml]

org.xml.sax.SAXParseException; lineNumber: 8; columnNumber: 109; schema_reference.4: Failed to read schema document
'http://www.springframework.org/schema/beans/spring-beans.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
...
Caused by: java.io.IOException: Server returned HTTP response code: 522 for URL: http://www.springframework.org/schema/beans/spring-beans.xsd
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:647)
at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:189)
at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:582)
at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:685)
at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaDOMParser.parse(SchemaDOMParser.java:530)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2171)
... 29 more

Exception in thread "main" org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 8 in XML document from class path resource [applicationContext-task.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 8; columnNumber: 109; cvc-elt.1: Cannot find the declaration of element 'beans'.
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:399)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
...
at org.springframework.context.support.GenericXmlApplicationContext.<init>(GenericXmlApplicationContext.java:69)
at com.jack.qingsonglvxing.b2b.Run.main(Run.java:9)
Caused by: org.xml.sax.SAXParseException; lineNumber: 8; columnNumber: 109; cvc-elt.1: Cannot find the declaration of element 'beans'.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
... 9 more
代码没有修改过,仅仅是重启而已,却报错了,而且错误信息很明确,
访问 http://www.springframework.org/schema/beans/spring-beans.xsd 文件报错,网络错误。
经过测试(国内访问测试、北美访问测试),可以确定,Spring的服务挂了(在2016年5月15日早上5点~5点半的某一时刻),如下图:

为何会出现,Spring服务挂了,会影响本地服务呢?

有些公司(尤其银行),在没有网络的情况下,也没有出现过该问题哪?

肯定是本地开发、部署、发布的某个环节存在BUG,从而导致该问题。

网上查询资料,果然发现了相同的问题,

博文(http://blog.csdn.net/bluishglc/article/details/7596118)

详细说明了Spring加载schema的原理和顺序,也提出了出现上述问题的原因。

内容转载如下:


---------------

 

-----------------------------------------------------

现在基本上都是采用maven来进行开发管理,我有如下需求:
需要把java工程打成可执行的jar包,必需把依赖的jar包也一起打包。
而maven默认的package命令构建的jar包中只包括了工程自身的class文件,并没有包括依赖的jar包。
可以通过配置maven-assembly-plugin插件来打包,pom具体配置如下:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<!--<addClasspath>true</addClasspath>-->
<mainClass>com.xxx.MainClass</mainClass>
</manifest>
</archive>
<descriptors>
<descriptor>src/main/assembly/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

其中<mainClass></mainClass>的值表示此工程的入口类,也就是包含main方法的类。

配置完pom后可以通过执行mvn assembly:assembly命令来启动插件进行构建。

构建成功后会生成jar包,这样我们就可以在命令行中通过java -jar XXX.jar来运行jar件了。
不过使用此插件会有一些问题:

工程中依赖了spring框架的jar包,打包成功后使用命令来调用jar包时报错如下(内网环境):
org.xml.sax.SAXParseException: schema_reference.4:
Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd',
because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.

究其原因,在网上找到一篇文章对此有比较详细的解释:

http://blog.csdn.net/bluishglc/article/details/7596118

简单来说就是spring在启动时会加载xsd文件,它首先会到本地查找xsd文件(一般都会包含在spring的jar包中),如果找不到则会到xml头部定义的url指定路径下中去寻找xsd,如果找不到则会报错。

附:在spring jar包下的META-INF文件夹中都会包含一个spring.schemas文件,其中就包含了对xsd文件的路径定义,具体如下图所示:

由于我的工程在内网,所以通过url路径去寻找肯定是找不到的,但是比较奇怪的是既然spring的jar包中都会包含,那为什么还是找不到呢?

原来这是assembly插件的一个bug,具体情况参见:http://jira.codehaus.org/browse/MASSEMBLY-360

该bug产生的原因如下:

工程一般依赖了很多的jar包,而被依赖的jar又会依赖其他的jar包,

这样,当工程中依赖到不同的版本的spring时,在使用assembly进行打包时,只能将某一个版本jar包下的spring.schemas文件放入最终打出的jar包里,这就有可能遗漏了一些版本的xsd的本地映射,所以会报错,所以一般推荐使用另外一个插件(maven-shade-plugin)来打包。

shade插件打包时在对spring.schemas文件处理上,它能够将所有jar里的spring.schemas文件进行合并,

在最终生成的单一jar包里,spring.schemas包含了所有出现过的版本的集合。

maven-shade-plugin插件配置如下:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<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.ManifestResourceTransformer">
<mainClass>com.xxx.MainClass</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
上面这段配置意思是把spring.handlers和spring.schemas文件以append方式加入到构建的jar包中,这样就不会存在出现xsd找不到的情况。

配置完pom后,调用mvn clean install命令进行构建,构建成功后打开工程target目录,发现生成了2个jar包,

一个为:original-XXX-0.0.1-SNAPSHOT.jar,

另一个为:XXX-0.0.1-SNAPSHOT.jar,

其中 original...jar里只包含了工程自己的class文件,而另外的一个jar包则包含了工程本身以及所有依赖的jar包的class文件。

我们只需要使用第二个jar包就可以了。

PS,写在后面,本博文完成之际,Spring服务已经恢复。

----------------------

Maven 如何打包可运行jar包

可以执行jar

拥有函数入口(public static void main(String[] args) {}),我们可以通过java -jar xxx.jar 来执行进入这个main函数。打成可执行效果,是通过mainfest.mf文件来指定的。因此如果不依赖其他工具进行打包,需要手动添加 MANIFEST.MF 到 META-INF/MANIFEST.MF
例如:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: wangzhiping
Created-By: Apache Maven 3.1.0
Build-Jdk: 1.8.0_101
Main-Class: wzp.study.maven.mainclass.HelloWorld

maven 打包可执行jar

maven-jar-plugin

  • pom.xml 配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>study.wzp.maven</groupId>
  <artifactId>maven-hello</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>maven-hello</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <!-- META-INF/MANIFEST.MF 添加 Main-Class: -->
              <mainClass>study.wzp.maven.App</mainClass>

              <!-- META-INF/MANIFEST.MF 添加 ClassPath: 外部依赖指定  -->
              <addClasspath>true</addClasspath>

              <!-- META-INF/MANIFEST.MF : 指定依赖包所在目录前缀 -->
              <classpathPrefix>/lib</classpathPrefix>
            </manifest>
          </archive>
        </configuration>
      </plugin>

      <!-- 自动实现将依赖拷贝到 lib 目录下,不然需要手动的执行-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>

            <goals>
              <goal>copy-dependencies</goal>
            </goals>

            <configuration>
              <!-- ${project.build.directory} 这是项目属性,后续篇章会有讲述 -->
              <outputDirectory>${project.build.directory}/lib</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: wangzhiping
Class-Path: /lib/junit-4.10.jar /lib/hamcrest-core-1.1.jar
Created-By: Apache Maven 3.1.0
Build-Jdk: 1.8.0_101
Main-Class: study.wzp.maven.App
  • 生成的目录
    这里写图片描述
    这里写图片描述
使用maven-jar-plugin,存在外部依赖时,需要指定外部依赖的位置(建议使用:maven-dependency-plugin)帮助管理,不能打包在一块,感觉不是很方便。

详细参考: https://maven.apache.org/plugins/maven-jar-plugin/

maven-shade-plugin

  • pom.xml 配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>study.wzp.maven</groupId>
  <artifactId>maven-hello</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>maven-hello</name>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
缺少点东西, 因此在有spring的文件时, 在局域网内部启动可能有点问题,参见前面的问题汇总
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>study.wzp.maven.App</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
  • MANIFEST.MF
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: wangzhiping
Created-By: Apache Maven 3.1.0
Build-Jdk: 1.8.0_101
Main-Class: study.wzp.maven.App
  • 项目目录
    这里写图片描述
会生成maven-hello-1.0-SNAPSHOT.jar 和 origin-maven-hello-1.0-SNAPSHOT.jar 两个打包文件,
1、maven-hello-1.0-SNAPSHOT.jar :将依赖一起合并打包的jar;
2、origin-maven-hello-1.0-SNAPSHOT.jar:不包含任何依赖的jar,如果存在依赖,那么这个文件会报错;

详细使用参考:https://maven.apache.org/plugins/maven-shade-plugin/

maven-assembly-plugin

  • pom.xml 配置
<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>study.wzp.maven.App</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>with-dependencies</descriptorRef>
          </descriptorRefs>

        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
</plugin>
  • 项目目录
    与shade相比,基本一致,会生成两个文件
1、maven-hello-1.0-SNAPSHOT.jar:无依赖关系,不可执行;
2、maven-hello-1.0-SNAPSHOT-jar-with-dependencies.jar:依赖合并打包,可执行。

详细可参考:https://maven.apache.org/plugins/maven-assembly-plugin/

总结

如果打包可执行jar,建议使用maven-shade-plugin或者maven-assembly-plugin,一起打包会更好些。可以直接执行。

现在很多人都 在用springboot了, 这个还是很好的, 不过我的经验是, 若是一切顺利的情况下, springboot是很好的, 但是若是万一有什么特殊需求, 在比较不顺利情况下, 很不好解决, 分析原因是springboot封装的过多了(个人意见)。