Решаем проблемы с загрузкой Spring Boot Jar


image


Сталкивались ли вы с проблемой запуска нового загрузочного архива 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.


Ладно, проблема понятна. Решение?


Было три варианта.


  1. Сделать свой spring-загрузчик ресурсов. Вариант отпал, поскольку, как я уже сказал, Томкат отрабатывает jsp до их запуска.
  2. Прокинуть загрузку в Томкат. Стал прикидывать: надо расширить контекст spring-а, прокинуть пути в контекст Томката, там ещё раза два переложить — непонятно, насколько сложно, и можно ли без изменения самого томката. Спрингисты не осилили, и я не хочу.
  3. Вариант попроще — пакуем ресурсы в ресурсную либу в 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.