Проблема с загрузкой Spring Boot Jar
Сталкивались ли вы с проблемой запуска нового загрузочного архива Spring Boot?
Вообще, новация в этом направлении уже не первая, стандартов
особых нет. Поэтому многие огребают проблемы и решают их на форумах
и стек-оверфлоу.
Если вы также столкнулись с проблемой, я помогу её решить. В таком
случае читаем дальше.
Итак, проблем с BootJar на самом деле хватает. Особенно учитывая, что уже минимум три версии формата поменялось.
Здесь я расскажу о часто встречающемся случае с потерей ресурсов. Конкретнее в моём случае с потерей JSP шаблонов. Почему JSP? Мне так привычнее, проект я по-быстрому начал с них, и не думал, что будут такие проблемы.
Итак структура проекта (стандартный веб):
/src/main/ java/ resources/ static/ some.html public/ webapp/ WEB-INF/jsp index.jsp
По заявлению создателей BootJar / BootWar, jsp не поддерживается толком в новом BootJar формате. Но совместимость должна быть в BootWar. На это я и надеялся, когда ваял код. Пока ваял, никаких проблем всё запускается, всё работает, как обычно, в общем. BootRun отрабатывает, только опции успевай подставлять.
Проблема пришла, откуда не ждали: когда пришло время разворачиваться на Амазоне, тогда и вылезла.
Итак, дубль раз. Чуть ли не в первый раз запускаю задачу
BootJar. Ярхив готов, деплоим Готово! Сигнала нет, сыпет ошибки 302
+ 404 (это авторизация не находит вью). Но это пока не понятно.
Отключаем Секурити всё равно ничего не находит, кроме голимой
статики, и то не всей, а только из webjars. ???
Дубль два. Догадываясь о несовместимости jsp и BootJar, пакуем BootWar. Деплоим Не работает. Не помню точно, но примерно то же самое в результате.
Хм, странно. Запускаем BootJar локально всё работает. Чудеса.
Выяснилось: Spring Boot слишком умный, он при запуске из каталога разработки даже релизного ярника всё равно все тащит из каталога разработки. Из другого запускаем перестаёт работать. Фух! Ну хоть отлаживать можно.
Что и делам. Выясняется ресурсы BootJar запакованные не из библиотек (webjars), а из проекта, не включаются в перечисление, и это, оказывается, по дизайну! Подробности здесь.
Вернее не так есть спец-ресурсы в каталогах типа static/,
public/. И они вроде находятся, если объявить. Но jsp не видит в
упор хоть тресни. И дело не в том, что не там лежат. Оказалось, что
Томкат (в нашем плохом случае), грузит jsp особым механизмом после
редиректа. И сами jsp можно загрузить без рендеринга, если
правильно задать их положение в настройках
spring.resources.static-locations
Но это нас не устраивает.
Оказалось, что при использования встроенного томката, ресурсы вью
он грузит отдельно и в первую очередь своей старой встроенной
логикой, которую Спрингисты настраивать не научились. А этой логике
нужен либо архив Вар, либо он же распакованный (почему кстати при
разработке нормально отрабатывает расположение webapp/), либо
ресурсы из библиотек, которые прекрасно видны, если правильно
упакованы в изначальных либах нужно чтоб лежали в
META-INF/resources, как в стандарте сервлетов. Последнее работает
даже внутри BootJar. Удивительно.
Почему не сработал распакованный архив? Ё-маё, на амазоне он распакуется черти-куда, и приложение про это место ничего не знает, если не сказать. Но хардкодить пути плохая привычка. Сам ярник запускается затем безо всякой распаковки, хотя вроде права позволяют распаковать прям на месте. Ну да ладно, способ пролетел.
Почему не работает способ с Вар-архивом? Ё-маё, Амазон решил, что лучше меня знает, что я буду грузить именно в яр-формате, хотя интерфейс заявлет о готовности съесть варник. Он этот варник переименовывает в ярник, умник такой. А Томкат не умничает, он смотрит расширение: не Вар ну тогда, извините, это не мой случай.
В итоге корень деплоя не находится. И ресурсы оттуда тоже. Ресурсы из статики не грузятся, потому что ищутся в корне и ресурсных либах, а не в classpath.
Ладно, проблема понятна. Решение?
Было три варианта.
- Сделать свой spring-загрузчик ресурсов. Вариант отпал, поскольку, как я уже сказал, Томкат отрабатывает jsp до их запуска.
- Прокинуть загрузку в Томкат. Стал прикидывать: надо расширить контекст spring-а, прокинуть пути в контекст Томката, там ещё раза два переложить непонятно, насколько сложно, и можно ли без изменения самого томката. Спрингисты не осилили, и я не хочу.
- Вариант попроще пакуем ресурсы в ресурсную либу в BootJar.
Вот о нём подробнее. Пихать всё в отдельный модуль, как
предлагали здесь, не хотелось.
Делаем отдельной задачей в Gradle.
Создаём конфигурацию.
sourceSets { jsp { resources.source(sourceSets.main.resources); resources.srcDirs += ['src/main/webapp']; } jmh { .. .. }}
Сама задача
task jsp(type: Jar, description: 'JSP Packaging') { archiveBaseName = 'jsp' group = "build" def art = sourceSets.jsp.output from(art) { exclude('META-INF') into('META-INF/resources/') } from(art) { include('META-INF/*') into('/') } dependsOn(processJspResources)}
Задача processJspResources уже создана, её не надо делать. Ставим всё в зависимость и пакуем:
bootJar { dependsOn(jsp) bootInf.with { from(jsp.archiveFile) { include('**/*.jar') } into('lib/') }}
Как добавить другим способом (прямым), не нашёл подключить в зависимости конфиг jspImplementation самого же проекта нельзя, а хотелось бы. Но если все же из другого модуля забирать, то вот так ещё делаем:
artifacts { jspImplementation jsp}
Всё, теперь имеем ресурсную либу, которую по всем стандартам томкат должен грузить, и он грузит. Запускаем, как BootJar.