SpringBoot ClassPathResource获取文件(包含一个坑,两个知识点)

问题

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,一个是JVMclasspath,也就是我们常说的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和打包文件。

  1. SpringBoot打的Jar和配置文件分离
  2. 修改jar中的MANIFEST.MF修改为通用的内容
  3. 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中存在的问题
  • 四个插件的含义和使用

おすすめ

転載: blog.csdn.net/qq_30038111/article/details/116992160