You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tomcat运行时main方法可读取文件 业务方法报FileNotFoundException

Java main方法与Tomcat环境下文件读取逻辑不一致问题

问题现象

  • 本地直接运行类中的main方法测试时,传入正斜杠、反斜杠两种格式的src/main/Java/archive/trawler/webservices/emailHTMLTemplates/resetMail.html相对路径,都能正常读取到resetMail.html文件,无报错,测试代码如下:
public static void main(String[] args) throws IOException, URISyntaxException {
    System.out.println(htmlToString("src/main/Java/archive/trawler/webservices/emailHTMLTemplates/resetMail.html"));
    System.out.println(htmlToString("src\\main\\Java\\archive\\trawler\\webservices\\emailHTMLTemplates\\resetMail.html"));
}
  • 部署到Tomcat服务器后,调用sendMailWithToken业务方法,传入完全相同的相对路径读取文件时,抛出java.io.FileNotFoundException异常,提示系统找不到指定路径,业务代码与异常栈如下:
public static boolean sendMailWithToken(String sendTo, String subjectLine, String token) throws FileNotFoundException {
        String htmlMessage = htmlToString("src\\main\\Java\\archive\\trawler\\webservices\\emailHTMLTemplates\\resetMail.html");
java.io.FileNotFoundException: src\main\Java\archive\trawler\webservices\emailHTMLTemplates\resetMail.html (The system cannot find the path specified)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
    at java.base/java.io.FileReader.<init>(FileReader.java:60)
    at archive.trawler.webservices.SendEmail.htmlToString(SendEmail.java:161)
    at archive.trawler.webservices.SendEmail.sendMailWithToken(SendEmail.java:121)
    at archive.trawler.webservices.accountResetResource.resetWachtWoord(accountResetResource.java:28)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
[...]
at setup.HttpsFilter.doFilter(HttpsFilter.java:28)
at javax.servlet.http.HttpFilter.doFilter(HttpFilter.java:57)
  • 测试阶段硬编码绝对路径修改htmlToString方法后可以正常读取,其余相对路径写法均无法定位目标文件,测试代码如下:
public static String htmlToString(String htmlFileFromRoot) throws IOException {
    StringBuilder contentBuilder = new StringBuilder();
    try {
//            BufferedReader in = new BufferedReader(new FileReader(htmlFileFromRoot));
        BufferedReader in = new BufferedReader(new FileReader("/resetmail.html"));
        String str;
        while ((str = in.readLine()) != null) {
            contentBuilder.append(str);
        }
        in.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return contentBuilder.toString();
}

项目目录结构参考:
项目目录树

问题根因

  • Java中FileReader等文件系统API解析相对路径时,基准路径是JVM启动时的工作目录,和类文件位置、源码目录位置没有绑定关系。
  • 本地通过IDE运行main方法时,IDE默认将JVM工作目录设置为项目根目录,因此写的src/main/xxx源码路径刚好能匹配到本地文件系统的路径,同时Windows系统本身兼容正斜杠和反斜杠作为路径分隔符,因此不会报错。
  • 项目部署到Tomcat后,JVM的工作目录是Tomcat自身的bin目录(根据启动方式不同可能有差异,但绝对不会是本地开发时的项目根目录),传入的src/main/xxx路径在Tomcat的工作目录下完全不存在,自然抛出文件找不到异常。
  • 项目打包部署为war包放到Tomcat运行时,源码目录src会被编译结构替换,根本不会保留src/main/Java/xxx这种源码层级的目录结构,就算手动修改Tomcat的工作目录为项目根目录,也找不到对应路径。
  • 硬编码系统绝对路径的写法仅能在当前测试机器上生效,一旦换部署环境、改文件部署位置就会直接失效,完全不具备可移植性,不能用于生产环境。

修复方案

  • 调整资源文件存放位置,符合Maven标准项目结构:把resetMail.html从当前src/main/java下的目录,移动到src/main/resources/emailHTMLTemplates/resetMail.html,项目编译打包时会自动把resources目录下的文件复制到classpath根路径下。
  • 修改文件读取逻辑,不要依赖文件系统相对路径,改用类加载器从classpath读取资源流,跨环境兼容,代码如下:
public static String htmlToString() throws IOException {
    StringBuilder contentBuilder = new StringBuilder();
    // 类加载器默认从classpath根路径查找资源,路径不要以/开头,统一使用正斜杠作为分隔符
    try (InputStream is = SendEmail.class.getClassLoader().getResourceAsStream("emailHTMLTemplates/resetMail.html");
         BufferedReader in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
        String str;
        while ((str = in.readLine()) != null) {
            contentBuilder.append(str);
        }
    }
    return contentBuilder.toString();
}
  • 额外注意事项:
    • 所有classpath资源路径统一使用正斜杠/作为分隔符,不要使用Windows专属的反斜杠,否则在Linux等非Windows系统部署时会出现路径解析错误。
    • 不要使用File、FileReader这类依赖本地文件系统的API读取项目内置的静态资源,这类资源最终打包在war/jar包内,本身不是文件系统上的独立文件,只有通过类加载器读取流的方式能兼容所有部署场景。
    • 如果不想调整文件位置,也可以通过配置maven-resources-plugin,将java目录下的html后缀文件纳入资源编译范围,但这种写法不符合常规项目规范,不推荐使用。

内容的提问来源于stack exchange,提问作者Ryan Reddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 23:48:39