FileNotFoundException cannot be resolved to absolute ...
in Debugging
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;
}
}
비효율적인 것 같지만 어쩌겠나.. 일단 돌리고 봐야지 😢