Как работать с Resource в Spring?
Что проверяют
Знаете ли вы абстракцию Resource и, главное, чем чтение файла из classpath отличается от чтения с диска, когда приложение упаковано в JAR. Следом часто спрашивают про префикс classpath*: и про внедрение ресурса через @Value.
Как отвечать
org.springframework.core.io.Resource это единый интерфейс над источниками данных. Ключевые методы: getInputStream(), exists(), isReadable(), getFilename(), getURL(), getFile(). В Spring 6 добавились удобные getContentAsString(Charset) и getContentAsByteArray().
Реализации:
ClassPathResource: ресурс из classpath, в том числе изнутри JAR;FileSystemResource: файл на диске;UrlResource:http:,https:,file:и другие URL;ByteArrayResourceиInputStreamResource: данные в памяти или готовый поток;ServletContextResource: ресурс веб-приложения относительно корня контекста.
Создавать ресурс конструктором приходится редко. Любой ApplicationContext сам является ResourceLoader и выбирает реализацию по префиксу: classpath:, file:, https:. Без префикса тип зависит от вида контекста. Самый короткий способ в Spring Boot: @Value("classpath:templates/mail.html") Resource template.
Для поиска по маске есть ResourcePatternResolver: classpath*:db/*.sql собирает совпадения со всех JAR в classpath, а classpath: смотрит только в первое найденное место.
Читать содержимое нужно через getInputStream() в try-with-resources или через getContentAsString(StandardCharsets.UTF_8), который закроет поток сам.
Где спотыкаются
Вызывают getFile() или Paths.get(resource.getURI()) для ресурса из classpath. В IDE это работает, а в собранном fat JAR падает с FileNotFoundException: файла на диске нет, он внутри архива. Нужен getInputStream().
Путают classpath: и classpath*:, и при нескольких модулях подхватывается только один конфиг или один набор миграций.
Не закрывают поток из getInputStream(). Каждый вызов открывает новый поток, и закрывать его обязан вызывающий код.