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后缀文件纳入资源编译范围,但这种写法不符合常规项目规范,不推荐使用。
- 所有classpath资源路径统一使用正斜杠
内容的提问来源于stack exchange,提问作者Ryan Reddy
相关产品推荐
相关产品推荐

