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

Spring Boot项目写入静态文件遇FileNotFound错误求助

Spring Boot静态文件写入踩坑:FileNotFound异常解决

最近我在Spring Boot项目里尝试写入静态文件的时候碰到了个头疼的问题——访问localhost:8080/data/user.json能正常获取到文件,但前端触发写入操作时直接抛出了FileNotFound错误,项目结构如下:

项目结构截图

先给大家看看相关的代码片段:

UserController代码

@Controller
public class UserController {

    UserService userService = new UserServiceImpl();

    @RequestMapping("/")
    public String getIndex(){
        return "/index.html";
    }

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    @ResponseBody
    public Response<Boolean> register(String user, String password)throws IOException{
        System.out.println("Get Register");
        System.out.println("user" + "  " + user);
        System.out.println("password" + " " + password);

        return userService.saveUser(user, password);
    }
}

FileTool工具类代码

public class FileTool {

    public static boolean writeFile(String filePath, String content) throws IOException{
        try {
            File file = new File(filePath);
            FileWriter writer = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(writer);
            printWriter.write(content);
            System.out.println(content);
            printWriter.println();
            writer.close();
            printWriter.close();
            return true;
        }catch (IOException ex){
            ex.printStackTrace();
        }

        return false;
    }
}

前端index.html代码

<!DOCTYPE html>
<html>
<input type="text" id="username" name="username" />
<input type="password" id="password" name="password"/>
<input type="button" id="btn" value="login"/>
<input type="button" id="btn2" value="register"/>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
    $(function () {
        $("#btn").click(function () {
            var user = $('#username').val();
            var password = $('#password').val();
            $.ajax({
                type:"GET",
                url:"/login",
                dataType:"json",
                data : {
                    user: user,
                    password: password
                },

                success : function (data) {
                    if(data.success){
                        alert("Success!");
                    }else
                        alert("Error!");
                },
                error : function () {
                    alert("Network warning");
                }
            });
        });

        $("#btn2").click(function () {
            var user = $('#username').val();
            var password = $('#password').val();
            $.ajax({
                type:"POST",
                url:"/register",
                dataType:"json",
                data : {
                    user: user,
                    password: password
                },
                success : function (data) {
                    if(data.success){
                        alert("Success!");
                    }else
                        alert("Error!");
                },
                error : function () {
                    alert("Network warning");
                }
            });
        });
    });
</script>
</html>

当我在FileTool里用data/user.json这个路径时,抛出了如下错误:

java.io.FileNotFoundException: data/user.json (No such file or directory)
at java.io.FileOutputStream.open0(Native Method)
at java.io.FileOutputStream.open(FileOutputStream.java:270)
at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
at java.io.FileWriter.<init>(FileWriter.java:90)
at com.utag.phase1.util.FileTool.writeFile(FileTool.java:12)
at com.utag.phase1.dao.UserDaoImpl.saveUser(UserDaoImpl.java:20)
at com.utag.phase1.service.Impl.UserServiceImpl.saveUser(UserServiceImpl.java:19)
at com.utag.phase1.controller.UserController.register(UserController.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

问题根源与解决办法

为什么会报错?

你用的data/user.json是相对路径,这个路径是相对于Spring Boot项目运行时的工作目录(比如IDE里运行时是项目根目录,打包成jar运行时是jar所在的目录),而不是你静态资源所在的src/main/resources/static目录。而且就算你找到这个路径,当项目打包成jar后,静态资源是在jar包内部的,根本没法直接写入压缩包里的文件。

正确的路径写法方案

1. 开发阶段用绝对路径(仅测试用)

直接写本地项目里静态文件的绝对路径,比如:

String filePath = "C:/your-project/src/main/resources/static/data/user.json";

缺点是换机器或者部署环境就失效,不适合生产。

2. 生产环境用外部目录(推荐)

在application.properties里配置一个外部存储路径:

file.storage.path=/opt/app-data/  # Linux环境
# 或者Windows环境:file.storage.path=C:/app-data/

然后在代码里注入这个配置,拼接路径写入:

@Service
public class UserServiceImpl {
    @Value("${file.storage.path}")
    private String storagePath;

    public Response<Boolean> saveUser(String user, String password) throws IOException {
        String filePath = storagePath + "user.json";
        // 调用FileTool写入
        boolean result = FileTool.writeFile(filePath, "your-content");
        return new Response<>(result);
    }
}

这种方式不管项目怎么部署,只要配置好路径就能正常写入,而且不会影响jar包内部文件。

3. 开发阶段写入classpath路径(仅调试)

如果只是开发时想写入src/main/resources下的文件,可以通过类加载器获取classpath的真实路径:

String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String filePath = classpath + "static/data/user.json";

注意:打包成jar后这个方法无效,因为jar是压缩包,无法写入内部文件。

额外优化:资源自动关闭

你的FileTool里的流关闭可以用try-with-resources语法,避免忘记关闭导致资源泄漏:

public static boolean writeFile(String filePath, String content) throws IOException{
    try (FileWriter writer = new FileWriter(filePath);
         PrintWriter printWriter = new PrintWriter(writer)) {
        printWriter.write(content);
        System.out.println(content);
        printWriter.println();
        return true;
    } catch (IOException ex){
        ex.printStackTrace();
        return false;
    }
}

内容的提问来源于stack exchange,提问作者Yu.Pan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:37:04