可运行jar包执行getResource返回null问题

SpringBoot中存在多个Filter时如何控制它们的执行顺序

Posted by Belin on March 9, 2019

正所谓前人栽树,后人乘凉。

感谢Huxpro提供的博客模板

1. 场景

起因:springboot项目打出可运行jar包,为减小jar体积方便更新,需要分离lib。 过程:使用其他打包插件替换springboot的打包插件,顺便贴代码

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<addClasspath>true</addClasspath>
				<classpathPrefix>lib/</classpathPrefix>
				<mainClass>xxx.main</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>copy-dependencies</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>copy-dependencies</goal>
			</goals>
			<configuration>
				<outputDirectory>
					${project.build.directory}/lib
				</outputDirectory>
				<overWriteReleases>false</overWriteReleases>
				<overWriteSnapshots>false</overWriteSnapshots>
				<overWriteIfNewer>true</overWriteIfNewer>
			</configuration>
		</execution>
	</executions>
</plugin>

由此得到:

命令行运行

java -jar TestDemo.jar

出现异常:

org.springframework.beans.factory.BeanInitializationException: 
Could not load properties; nested exception is java.io.FileNotFoundException: 
class path resource [] cannot be resolved to URL because it does not exist

找到异常代码:

ResourceUtils.getURL("classpath:").getPath()

郁闷至极,不就是把lib包分离出来吗?代码正常启动说明lib包和classes的类都在classpath中,然而缺获取不到类根路径…

2. 问题原因

经过多次调试、google终于发现问题所在,下面是问题原因: 首先上面代码ResourceUtils.getURL("classpath:")等价于classLoader.getResource(""),其中classLoader要根据环境而定不同环境可能不同; 其次要理解getResource的含义:

getResource:主要是获取资源,这个资源并不仅限于文件,还有文件夹。

最后,通过以下代码

String jarName = "xxxx.jar";
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(jarName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Enumeration<JarEntry> entrys = jarFile.entries();
        while (entrys.hasMoreElements()) {
            JarEntry jarEntry = entrys.nextElement();
            System.out.println(jarEntry.getName());
        }

会发现,jarentry中并没""这个目录资源(这是肯定的,都是空字符串了),所以前面的异常代码就抛异常了。

那么问题来了,为什么springboot的打包插件又能正常运行? 比较springboot打包的jar和上文maven打包的jar,发现jar包的目录结构有所差别:

  • 1、上文maven打包的jar包根路径直接是classes,即类路径的根路径
  • 2、springboot打包插件打包的jar是BOOT-INF、META-INF、org,此时类路径的根路径是BOOT-INF/classes

这个根路径的差别与ClassLoader有关,第一的类加载器是APPClassLoader, 第二的类加载器是LaunchedURLClassLoader。 第二情况的时候getResource(“”)等价于 getResource(“BOOT-INF/classes”)`,所以springboot打包插件打包的jar能获得根路径。

3. 解决办法

重新配置打包插件,仍然使用springboot打包插件打包,如下

<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
	<!--<executable>true</executable>-->
		<layout>ZIP</layout>
		<includes>
			<include>
				<groupId>nothing</groupId>
				<artifactId>nothing</artifactId>
			</include>
		</includes>
	</configuration>
</plugin>
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-jar-plugin</artifactId>
	<configuration>
		<archive>
			<manifest>
				<addClasspath>true</addClasspath>
				<classpathPrefix>lib/</classpathPrefix>
				<mainClass>xxx.main</mainClass>
			</manifest>
		</archive>
	</configuration>
</plugin>
<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-dependency-plugin</artifactId>
	<executions>
		<execution>
			<id>copy-dependencies</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>copy-dependencies</goal>
			</goals>
			<configuration>
				<outputDirectory>
					${project.build.directory}/lib
				</outputDirectory>
				<overWriteReleases>false</overWriteReleases>
				<overWriteSnapshots>false</overWriteSnapshots>
				<overWriteIfNewer>true</overWriteIfNewer>
			</configuration>
		</execution>
	</executions>
</plugin>

4. 问题复现最小demo

注意maven打包仍是最前面的配置

5. 问题相关知识

  • 1、类加载机制
  • 2、jar包目录结构
  • 3、add directory entries (这里指eclipse导出jar包常见的问题)