SpringBoot ClassPathResource获取文件(包含一个坑,两个知识点)
问题SpringBoot项目,在Idea中无论怎么运行,都是正常的,通过maven打包的jar包运行时,ClassPathResource#getFile()方法,始终报错,找不到文件。代码如下:配置Freemarker的模板路径报错如下:不论是文件还是文件夹,都会报错13:52:12.405 ERROR [main] com.edu.compile.parse.FreemarkerParse:3
问题
SpringBoot
项目,在Idea
中无论怎么运行,都是正常的,通过maven
打包的jar
包运行时,ClassPathResource#getFile()
方法,始终报错,找不到文件。
代码如下:配置Freemarker
的模板路径
报错如下:不论是文件还是文件夹,都会报错
13:52:12.405 ERROR [main] com.edu.compile.parse.FreemarkerParse:35 - freemarker init failed: class path resource [template/ftl/] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/E:/dev_file/git/lowcode/target/lowcode-1.0.jar!/BOOT-INF/classes!/template/ftl/
13:52:12.424 INFO [main] com.edu.control.config.RetryQueueStarter:95 - init retry task delay queue ...
13:52:13.613 INFO [main] o.s.s.concurrent.ThreadPoolTaskExecutor:171 - Initializing ExecutorService
13:52:13.614 INFO [main] o.s.s.concurrent.ThreadPoolTaskExecutor:171 - Initializing ExecutorService 'deployThreadPool'
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Parsed mapper file: 'class path resource [mapper/EventDefineMapper.xml]'
Parsed mapper file: 'class path resource [mapper/EventNodeMapper.xml]'
Parsed mapper file: 'class path resource [mapper/EventVersionMapper.xml]'
13:52:14.736 INFO [main] com.edu.compile.util.LocalNodeEnv:77 - start init Windows 10 node env
13:52:14.736 INFO [main] com.edu.compile.util.LocalNodeEnv:83 - node uncompressed status: false
13:52:14.739 ERROR [main] com.edu.compile.util.LocalNodeEnv:165 - node check error: Cannot run program "file:\E:\dev_file\git\lowcode\target\lowcode-1.0.jar!\BOOT-INF\classes!\node-win-x64/bin/node.exe": CreateProcess error
网上的解决方案(对我来说不靠谱)
网上提供了几种解决方案,我只记住了两种:
第一种是ResourceUtils.getFile()
,网上说linux
下不通用,实际情况也会报相同的错误。
第二种是FreemarkerParse.class.getClassLoader().getResourceAsStream("");
通过读取流的方式,网上说这是一种通用的方式,确实是通用的方式,但是获取一个文件夹怎么弄?我的需求是获取当前的路径,而不是当前的文件。
问题分析
从报错信息来看jar:file:/E:/dev_file...
很明显,是从jar
文件中找东西的,握草,对于使用SpringBoot
不熟悉的小伙伴(我™在说我自己,毕太久没有用过它了),先从打包插件和打的包来看,SpringBoot package
仅仅只有一个jar
文件,在jar
文件中,包含了执行依赖,并且MANIFEST.MF
也是SpringBoot
重新定义了ClassPath
(忘了原始的是什么样子了?下面有)。
启动命令:java -jar xxx.jar
好好想一想,这个命令启动SpringBoot
项目,涉及了几个classpath
?
至少涉及了两个classpath
,一个是JVM
的classpath
,也就是我们常说的classpath
,另一个是SpringBoot
定义的classpath
。
插件:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<mainClass>com.edu.Application</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
jar:
解决方案
解决思路
既然SpringBoot
定义了一个classpath
,那我偏偏不用SpringBoot
提供的,我非要自定义classpath
和打包文件。
- 将
SpringBoot
打的Jar
和配置文件分离 - 修改
jar
中的MANIFEST.MF
修改为通用的内容 - 将
jar
和配置文件打成tar.gz
包,用于部署
盘他
spring-boot-maven-plugin(我不会用)
我说了SpringBoot
,我用的不熟,可能这个插件或者其他springboot
的插件有提供功能,但是我不知道,也不会用
resources、dependency、jar、assembly
我会用这四个插件解决问题
-
resources
将
/src/main/resources
下的所有文件,拷贝到target
下的conf
文件夹下<plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> <executions> <execution> <id>copy-resources</id> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/conf</outputDirectory> <resources> <resource> <directory>/src/main/resources</directory> <!-- filtering=true,拷贝的zip包会报错:不可预期的压缩文件末端 --> <filtering>false</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin>
-
dependency
将项目依赖的
jar
拷贝到target
下的lib
文件夹<plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin>
以上两个插件生成的效果图:
-
jar
打包
jar
包,定义MANIFEST.MF
文件,设置依赖的jar
的路径和配置文件的路径,其实相当于重新定义了classpath
。
注意:conf
前有两个点,依赖jar
的前缀有一个点(可以不填前缀),这两个东西和第四个插件相呼应。<plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <includes> <include>**/*.class</include> </includes> <archive> <manifest> <mainClass>${main.class}</mainClass> <addClasspath>true</addClasspath> <useUniqueVersions>false</useUniqueVersions> <classpathPrefix>./</classpathPrefix> </manifest> <manifestEntries> <Class-Path>../conf/</Class-Path> </manifestEntries> </archive> </configuration> </plugin>
生成的MANIFEST.MF:
-
assembly
这个是最复杂的插件,包含了一个子文件assembly.xml
,这个插件是将上面生成的conf、lib、jar
打成tar.gz
压缩包。
在assembly.xml
中,引用了上面插件生成的conf、lib
的文件路径,该插件会将打好的jar
放入lib
中,将conf、lib
打成tar.gz
压缩包。上面说conf
前有两个点,你明白了吗?因为jar
最终会被放入lib
中,所以必须在conf
前加上两个点,表示上级目录的文件夹。<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.1.0</version> <configuration> <finalName>${project.artifactId}</finalName> <appendAssemblyId>false</appendAssemblyId> <descriptors> <descriptor> /src/main/resources/assembly/assembly.xml </descriptor> </descriptors> </configuration> <executions> <execution> <id>assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
assembly.xml
<?xml version='1.0' encoding='UTF-8'?> <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>assembly</id> <formats> <format>tar.gz</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.build.directory}/conf</directory> <outputDirectory>conf</outputDirectory> <filtered>true</filtered> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/conf</directory> <outputDirectory>conf</outputDirectory> <filtered>false</filtered> <excludes> <exclude>**/*.xml</exclude> <exclude>**/*.properties</exclude> </excludes> </fileSet> <fileSet> <directory>${project.build.directory}/lib</directory> <outputDirectory>lib</outputDirectory> <filtered>false</filtered> </fileSet> </fileSets> <dependencySets> <dependencySet> <outputDirectory>lib</outputDirectory> </dependencySet> </dependencySets> </assembly>
效果图:
jar、assembly(终极解决方案)
上面使用了四个插件,太™多了,仅用jar、assembly
插件就可以搞定!
两个插件的配置基本不变,改变的仅仅是assembly
的子文件assembly.xml
,
修改的内容是${project.build.directory}/conf
替换为src/main/resources
。
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<includes>
<include>**/*.class</include>
</includes>
<archive>
<manifest>
<mainClass>${main.class}</mainClass>
<addClasspath>true</addClasspath>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
<manifestEntries>
<Class-Path>../conf/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<finalName>${project.artifactId}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>/src/main/resources/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<?xml version='1.0' encoding='UTF-8'?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>assembly</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>conf</outputDirectory>
<filtered>true</filtered>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>conf</outputDirectory>
<filtered>false</filtered>
<excludes>
<exclude>**/*.xml</exclude>
<exclude>**/*.properties</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
总结
通过自定义classpath
,可以让springboot
中的Resource#getFile()
表现出正确的行为。
坑
SpringBoot
中获取ClassPath
下的文件引出当前博客
知识点
- Resource#getFile()在SpringBoot中存在的问题
- 四个插件的含义和使用
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)