FileNotFoundException cannot be resolved to absolute ...

FileNotFoundException cannot be resolved to absolute ...

FileNotFoundException : cannot be resolved to absolute file path because it does not reside in the file system

 

❗ 증상


출퇴근길 지하철에서 사용할 토이 프로젝트를 개발하다 발생한 문제다.

이 문제는 Gradle을 사용해 프로젝트를 jar로 빌드하고 jar환경에서 실행하며 나타났다.

if-else를 제거하기 위해 resource 폴더 하위에 json 형식의 프로퍼티 파일을 작성해서 해결했는데,

배포하고 보니 로그에 계속 예외가 뜨는 걸 확인했다.

java.io.FileNotFoundException: class path resource [static/properties/propertiesFactory.json] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/home/ubuntu/SubscribeTechBlogs-1.0.jar!/BOOT-INF/classes!/static/properties/propertiesFactory.json
        at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:217)
        at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:154)
        at toy.subscribe.parser.JsonReader.readUrls(JsonReader.java:16)
        at toy.subscribe.service.impl.CollectPostServiceImpl.loopCrawl(CollectPostServiceImpl.java:32)
        at toy.subscribe.service.impl.CollectPostServiceImpl.getAllGroupFeed(CollectPostServiceImpl.java:28)
        at toy.subscribe.service.impl.CollectPostServiceImpl$$FastClassBySpringCGLIB$$dc747bec.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
        at toy.subscribe.service.impl.CollectPostServiceImpl$$EnhancerBySpringCGLIB$$3d4d0c29.getAllGroupFeed(<generated>)
        at toy.subscribe.scheduler.RSSScheduler.collect(RSSScheduler.java:21)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
        at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:834)

로컬에서는 아무런 문제가 없었다.

public class JsonReader {

    public static List<String> readUrls() throws Exception {
        File file = ResourceUtils.getFile("classpath:properties/propertiesFactory.json");
        FileReader reader = new FileReader(file);
        JSONParser parser = new JSONParser();
        JSONObject jsonObj = (JSONObject) parser.parse(reader);

        return (ArrayList<String>) jsonObj.get("urls");
    }
}

보다시피 classpath:를 이용해 resource 하위 폴더에서 json 파일을 읽어오고 있다.

✅ 해결


파일을 찾지 못한다니 의아했지만(로컬에선 잘됐으니까.. 🤔)

우선 경로를 점검해봤다.

jar -tf 파일명 // jar 내부의 내용을 확인하는 명령어
BOOT-INF/classes/static/images/favicon.ico
BOOT-INF/classes/static/images/github.jpg
BOOT-INF/classes/static/images/blog.png
BOOT-INF/classes/static/images/yanolja.png
BOOT-INF/classes/static/images/woowabros.png
BOOT-INF/classes/static/images/toss.png
BOOT-INF/classes/static/images/line.png
BOOT-INF/classes/static/css/
BOOT-INF/classes/static/css/style.css
BOOT-INF/lib/
BOOT-INF/lib/querydsl-apt-4.1.4.jar
BOOT-INF/lib/poi-ooxml-5.0.0.jar
BOOT-INF/lib/poi-5.0.0.jar
BOOT-INF/lib/querydsl-jpa-4.4.0.jar
BOOT-INF/lib/json-simple-1.1.1.jar
BOOT-INF/lib/mysql-connector-java-8.0.23.jar
BOOT-INF/lib/querydsl-codegen-4.1.4.jar
BOOT-INF/lib/jdo-api-3.0.1.jar
BOOT-INF/lib/spring-boot-autoconfigure-2.4.3.jar
BOOT-INF/lib/spring-boot-2.4.3.jar
BOOT-INF/lib/jakarta.transaction-api-1.3.3.jar
BOOT-INF/lib/jakarta.persistence-api-2.2.3.jar
BOOT-INF/lib/hibernate-core-5.4.28.Final.jar
BOOT-INF/lib/spring-data-jpa-2.4.5.jar
BOOT-INF/lib/spring-aspects-5.3.4.jar
BOOT-INF/lib/thymeleaf-spring5-3.0.12.RELEASE.jar
BOOT-INF/lib/thymeleaf-extras-java8time-3.0.4.RELEASE.jar
BOOT-INF/lib/jakarta.el-3.0.3.jar
BOOT-INF/lib/hibernate-validator-6.1.7.Final.jar
BOOT-INF/lib/spring-webmvc-5.3.4.jar

jar로 빌드하게되면 보다시피 내부의 경로가 바뀌므로

기존의 코드로는 읽지 못하게 되어 FileNotFoundException 예외가 발생하게 됐다.

이를 해결하기 위해 파일을 읽어오는 작업을 ClassLoader에 위임해야 했다.

따라서 내부 json 파일에 InputStream을 열고

InputStream을 활용해 File 객체를 생성한 후 사용했다.

public class JsonReader {

    public static List<String> readUrls() throws Exception {
        FileReader reader = new FileReader(createInputStreamToFile());
        JSONParser parser = new JSONParser();
        JSONObject jsonObj = (JSONObject) parser.parse(reader);

        return (ArrayList<String>) jsonObj.get("urls");
    }

    private static File createInputStreamToFile() throws IOException {
        InputStream inputStream = new ClassPathResource("static/properties/propertiesFactory.json").getInputStream();
        File file = File.createTempFile("propertiesFactory", ".json");
        try {
            FileUtils.copyInputStreamToFile(inputStream, file);
        }
        finally {
            IOUtils.closeQuietly(inputStream);
        }
        return file;
    }

}

 

비효율적인 것 같지만 어쩌겠나.. 일단 돌리고 봐야지 😢


© 2022. All rights reserved.