Подключаем Spring Boot к имеющемуся Java приложению

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Подключаем Spring Boot к имеющемуся Java приложению

Сообщение dtvims » Ср ноя 22, 2023 5:47 pm

Далее поставим задачу, опишем свою боль и героически решим все проблемы, ну или почти все. Я опишу основные моменты и те вопросы, которые сам задавал, остальное можно найти в учебниках.

Постановка задачи:
Имеется приложение, в котором используются классы HttpServer и HttpClient. Необходимо добавить новый функционал, а чтобы это было сделать легко (Ага #сарказм) подключить или перевести все на современные рельсы Spring.
(С) Лучше день потрять, потом за 5 минут долететь.

Боль проблем:
Новый функционал - это, в первую очередь, новый http-client и если мы говорим про Spring, то разумно использовать FeignClient, на что устаревшие версии Spring`а не способны, а значит речь о последней версии (на момент написания), т.е. Spring boot. Поскольку - это самостоятельное приложение со встроенным вэб сервером, а не сервлет для чего-то вроде Tomcat, то это наложит свои ограничения, о чем будет далее. А поскольку мы перерабатываем существующее приложение, то задача уже видится как нетривиальная, т.к. все инструкции и обучалки рассказывают нам как заточить приложение с нуля, а если мы не хотим перекраивать вообще все на свете на новые рельсы, то надо искать решения нестандартные. Как оказалось, да собственно так и бывает, что на все нестандартное никаких инструкций нет или почти нет. Основная боль, что хотели упростить, но все усложнили :).
Основная боль - это, что в учебниках только как начать с нуля, а все остальное есть в сети на форумах или в специфичных инструкциях, но если мы дорабатываем старое приложение, то готовьтесь, что информации не будет вообще...

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Подключаем Spring Boot

Сообщение dtvims » Ср ноя 22, 2023 5:51 pm

Первая проблема, что при доработке старого приложения по "устаревшим технологиям", надо как-то все подключить "по старому", т.е. не будет использовано удобного конструктора проектов Spring или Maven`а.
Spring приложение строится на аннотациях и бинах, что в свою очередь дает возможность просто подбросить в проект jar файл и весь его функционал автоматом подцепится, что также и создает проблему, если мы не подкинули нужную библиотеку/фреймворк, то не понятно по чему нужное нам не работает, т.к. ошибок нету от слова совсем... Сам запуск Spring boot, конечно - это инициализация конкретных классов, а потому тут понятно, что необходимо первоначально подключить. В конце я напишу какой именно список библиотек у меня сформировался, но главное понять как это все стартануть так, чтобы понять чего не хватает. Так же есть одна проблема, по которой я так и не нашел решения в сети, что и послужило причиной этой инструкции.

Минимальный набор у меня (но это не точно) сформировался такой (версии на текущий момент):

Код: Выделить всё

spring-aop-6.0.13.jar
spring-beans-6.0.13.jar
spring-boot-3.1.5.jar
spring-boot-autoconfigure-3.1.5.jar
spring-context-6.0.13.jar
spring-core-6.0.13.jar
spring-expression-6.0.13.jar


Тут основные либы, просто стартануть и если у них есть зависимости, то они должны об этом сказать (вроде).
точно понадобятся еще

Код: Выделить всё

slf4j-api-2.0.9.jar
slf4j-nop-2.0.7.jar
и
log4j-1.2.17.jar
log4j-api-2.21.1.jar
log4j-core-2.21.1.jar


еще, не помню когда, но потребовалась еще такая зависимость:

Код: Выделить всё

micrometer-commons-1.11.5.jar
micrometer-observation-1.11.5.jar


Но у нас задача использовать FeignClient, а для него тянутся еще куча зависимостей:

Код: Выделить всё

spring-cloud-commons-4.0.4.jar
spring-cloud-context-4.0.4.jar
spring-cloud-openfeign-core-4.0.4.jar
и
feign-core-12.4.jar
feign-form-3.8.0.jar
feign-form-spring-3.8.0.jar
feign-slf4j-12.4.jar


Т.к. мне нужен Rest, то до кучи еще:

Код: Выделить всё

jackson-annotations-2.15.3.jar
jackson-core-2.15.3.jar
jackson-databind-2.15.3.jar


А для удобства:

Код: Выделить всё

lombok-1.18.30.jar


Чтобы начать - этого должно хватить...

Так вот, чтобы стартануть Spring, в Main процедуре выполняется команда Run для SpringBoot (не буду давать инструкцию, т.к. этого добра навалом...)
Но такой запуск, не даст ошибок по зависимостям, потому для первых попыток рекомендую использовать запуск через создание руками объекта ApplicationContext и получение от туда бинов (это все есть в начальных уроках по Spring). Такой запуск заставляет систему сканировать, так сказать, систему в ручном режиме, тянуть нужные зависимости, и вызовать исключения о не найденных зависимостях, в то время как запуск Run, просто игнорирует часть зависимостей, про которые ничего не знает. В принципе, если включить DEBUG в настройках Логера (кстати, его необходимо обязательно настроить, иначе не запустится), то в логах будет информация о Бинах, которые нашел и которых НЕ нашел.

Log4j в моем приложении ранее использовался, потому его особо не настраивал, но запуск Spring должен осуществляется строго после инициализации log4j.

Перед главным классом ставим аннотации:

Код: Выделить всё

@SpringBootApplication
@EnableFeignClients
@EnableScheduling
public class MainApps {
   ...
}


Первая обязательная, далее включаются необходимые функции, которые не будут работать без этих аннотаций (EnableScheduling - по данной я ничего не писал, там все понятно итак).
Стартуем в главной функции наше приложение так:

Код: Выделить всё

      String finalSettingsPatch = SettingsPatch; //Если вам не надо настраивать как-то особо какой либо класс при инициализации, то это строка как и все что с Config.class Вам не нужно
        ApplicationContext contextMain = new SpringApplicationBuilder()
                .bannerMode(Banner.Mode.OFF) //Отключаем баннер заставку SpingBoot, которая есть только при таком способе запуска через Run
                .initializers((ApplicationContextInitializer<GenericApplicationContext>) context -> {
                    context.registerBean(Config.class, finalSettingsPatch); //Собственный класс конфига должен быть инициализирован первым и с параметрами, потому инициализируем его так...
                })
                .sources(MainApps.class)
                .run(args);
        Config cfg = (Config) contextMain.getBean("ru.myApp.cfg.Config"); // интересно, что бинов "Config.class" несколько, потому указать надо явно
        LOGGER.debug(cfg.toString()); // Проверяем то ли нашли.


Config.class - это исторический класс в моем старом приложении, что отвечает за настройки этого приложения, для работы Spring он не нужен, просто пример. Из строк Builder можно исключить любую, кроме run - это не повлияет на запуск, разве что без sources он может не найти ничего из текущего проекта (хз как так), но вместо sources можно использовать соответствующую аннотацию, см. любой учебник по Spring.
Заметим, что мой Конфиг, для корректной работы исходного приложения, инициализироваться должен один раз, и, несмотря на вызов конструкции registerBean с параметрами, getBean вызывается без параметров, но getBean найдет, инициализированый ранее объект, собственно как везде, где он будет нужен.

У класса Config есть только один конструктор с параметром, а автоматически Spring пытается создать Бин через конструктор Без параметров, потому перед классом Config нужны аннотации:

Код: Выделить всё

@Lazy
@Component


Где Lazy запрещает Spring`у самостоятельно инициализировать Бин при старте, до того момента, пока он не понадобится. Мы его инициализируем сами с параметром, а т.к. Spring делает только один его экземпляр, то далее проблем с его использованием не возникает.

Если необходимо использовать Бины, которые должны каждый раз создаваться новые, то перед классом надо поставить такой набор аннотаций:

Код: Выделить всё

@Lazy
@Component
@Scope("prototype")


Lazy тут нужен, чтобы Spring не создавал его при запуске, ну хотя бы потому, что он не нужен и никогда использоваться этот экземпляр не будет, а Scope с именем prototype как раз заставляет каждый раз создавать новый объект, при использовании getBean или @Autowired.
Лично я тут искал и проверял еще один момент, о котором при этом обычно не говорят. в Scope по умолчанию singleton (его указвать не надо) Spring берет на себя контроль за объектомв в памяти, а вот в случае prototype Spring только создает данный объект и забывает про его существование, потому далее его существование контролируется только Java и когда он не будет никому нужен, он будет автоматически уничтожен, т.е. удален из памяти как и любой класс Java, созданный оператором new. Фактически просто вместо new мы используем теперь getBean или @Autowired, но получившийся объект тепь получит все возможности Spring, т.е. в нем можно теперь использовать @Autowired и другие плюшки, иначе, для использования того же FeignClient, придется в класс как-то передать AplicationContext, т.к. FeignClient можно вытянуть только от туда, если он объявлен как interface и без proxy обертки от Spring, просто работать не будет.

Далее можно создавать класс с аннотацией @FeignClient и всего что рядом с ним и настраивать клиент, далее все по инструкции из учебников или на форумах или в др. инструкциях в сети.

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Настройка Web-сервера Spring Boot

Сообщение dtvims » Ср ноя 22, 2023 5:54 pm

Поскольку речь идет о доработке старого приложения, то тут надо выйти за рамки учебников.

Сперва добавим еще ряд jar`ников:

Код: Выделить всё

spring-boot-starter-3.1.5.jar
spring-boot-starter-web-3.1.5.jar
spring-web-6.0.13.jar
spring-webmvc-6.0.13.jar
tomcat-embed-core-10.1.15.jar
tomcat-embed-websocket-10.1.15.jar


Это то что надо добавить к проекту, чтобы заработала аннотация @Controller или @RestController
Тут есть интересный момент, что именно RestController возвращает просто строку, что необходимо вернуть клиенту, а Controller возвращает имя представления. Но последнее можно исправить, если сделать так для класса с аннотацией @Controller:

Код: Выделить всё

   @RequestMapping("/helloWorld")
    public ResponseEntity helloWorld() {
        String body = "Hello World!" ;
        return ResponseEntity.ok().body(body);
    }

Думаю тут проблем более ни с чем возникнуть не должно, все итак понятно, если нет, то go в учебники и ищем про ResponseEntity.

Что там я говорил про имя представления? Там возвращается имя шаблона String, как с этим работать много инструкций, потому не буду заострять внимание. Просто подключить html шаблон мне показалось не интересным или не универсальным и я захотел подключить JSP, что оказалось совсем не просто и именно это стало главной проблемой сего опуса!
Чтобы что-то заработало из моделей (шаблонов или представлений) необходимо просто в проект докинуть нужные jar файлы, Spring их сам подтянет, а если их нет, то просто будет ошибка, что нужный Mapper не найден и та в лог, если повезет :)

Итак нам понадобится класс:

Код: Выделить всё

@Configuration
@EnableWebMvc
public class MyConfiguration implements WebMvcConfigurer {
   ...
}


Аннотации сделают свое дело, Spring сам найдет этот класс и он будет использован. @EnableWebMvc - необходима только теперь, когда мы захотели использовать JSP.

Внутри класса, создаем метод:

Код: Выделить всё

   @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp();
    }


Он создаст Mapper (вернее только где искать файлы) для jsp, но пока ничего работать не будет. Вообще там же у объекта registry есть и другие методы для создания специфичных мапперов для обработки представлений.

Теперь в контроллере можно использовать обработчик:

Код: Выделить всё

   @RequestMapping("/helloWorld")
    public String helloWorld() {
        return "hello";
    }

Т.е. при запросе: "http://localhost/helloWorld" будет искаться файл в частности hello.jsp, ну или другие файлы с именем hello, а т.к. они не будут найдены или не будут найдены мапперы на них, то будет ошибка 404, что файл не найден.

Чтобы вообще файл нашелся необходимо положить его не абы куда, а именно в папку "src/main/webapp/WEB-INF". Если быть точнее, то искать файлы система будет по пути "src/main/webapp", а папка "WEB-INF" просто везде в мапперах по умолчанию используется. Т.о. образом, если у Вас в проекте нет такого пути, то его надо создать, вернее тупо создать папки и все. Теперь можно в папку "src/main/webapp/WEB-INF" бросить файл "hello.jsp"

Теперь все? Нет все-равно ошибка 404 или 500!
теперь к проекту надо докинуть еще jar файлы (возможно еще что-то понадобится, узнаете при запуске):

Код: Выделить всё

ecj-4.6.1.jar
jakarta.annotation-api-2.1.1.jar
jstl-1.2.jar
spring-boot-starter-tomcat-3.1.5.jar
tomcat-embed-el-10.1.15.jar
tomcat-embed-jasper-10.1.11.jar


Собственно spring-boot-starter создает запуск Tomcat`а с его мапперами, в часности JSP.
Если мы теперь запустим приложение, то все стартанет, но на запрос "http://localhost/helloWorld" мы получим ошибку 500, а в консоль, ого-го сколько ошибок (время из текста ошибок удалил):

Код: Выделить всё

  org.apache.jasper.compiler.JDTCompiler generateClass
WARNING: Unsupported source VM [11] requested, using [1.3]
  org.apache.jasper.compiler.JDTCompiler generateClass
WARNING: Unsupported target VM [11] requested, using [1.2]
  org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet [jsp] threw exception
org.apache.jasper.JasperException: Unable to compile class for JSP:

An error occurred at line: [32] in the generated java file: [C:\somePath\tomcat.8081.12926814797401455572\work\Tomcat\localhost\ROOT\org\apache\jsp\WEB_002dINF\hello_jsp.java]
Syntax error, parameterized types are only available if source level is 1.5 or greater
...


Что тут главное? Что JSP компилится с версией Java машины 11-ой, но JSP максимально поддерживает 8-ю, т.е. "1.8", но почему-то сбрасывает на "1.3", которая устарела на столько, что ... ну вообще ничего не работает.
Все поиски говорят, что нужно проинициализировать 2 параметра (ну и несколько других, но главное 2):

Код: Выделить всё

compilerTargetVM 1.8
compilerSourceVM 1.8


А вот как это сделать? Я перепробовал все варианты, но не работало ничего. web.xml с данными параметрами инициализации JSP, файл куда только не подкладывал, но его система или не видит или он неправильно сконфигурирован или вообще не используется. Работает это только в версиях на базе вэбсервера Tomcat, но не как SpingBoot приложение. Есть спец класс initializer для сервлетов, но он тоже не используется SpingBoot приложении, т.е. только для сервлетов в Tomcat.
В классе конфигураторе можно руками задать параметры инициализации с помощью метода setInitParameter, но они не работают, а не работают потому, что JSP сервлет создается Tomcat`том, в данном случае запущенном внутри SpingBoot, ну, т.е. его классом, а значит он инициализируется еще до установки данных параметров (ну я так понял в ходе кучи экспериметов).
В общем решение мной так и не было найдено на просторах сети. Но т.к. я пришел к выводу, что проблема в последовательности инициализации, то я решил попробовать, а можно ли что-то изменить уже после?
Используя знания полученные в поиске решения проблемы, я набросал слудющий код, который надо добавить в класс конфигурации (ищите примеры по registerPreCompiledJsps):

Код: Выделить всё

    @Bean
    public ServletContextInitializer registerPreCompiledJsps() {
        return servletContext -> {
            ServletRegistration sr = servletContext.getServletRegistration("jsp");
            sr.setInitParameter("compilerTargetVM", "1.8");
            sr.setInitParameter("compilerSourceVM", "1.8");
        };
    }


Путем не трудных путей отладки (правда до них нужно было еще дойти) я узнал, что сервлет с именем "jsp" уже есть (к этому я пришел, когда попытался создать его самостоятельно, но у меня это не вышло). А раз он есть, то можно ли его как-то заполучить? Ага, "getServletRegistration" возвращает как раз указатель на созданный ранее сервлет по имени. А можно ли его параметры поменять? А тут есть метод setInitParameter, но будет ли он работать? В общем я попробовал и получил заветный результат: "Добро пожаловать!" - именно это возвращает мой тестовый jsp файл, который мне первый попался для теста типа "Hello World".

Собственно - это самая сложная проблема была.

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Необычная проблема Spring вэб сервера

Сообщение dtvims » Ср ноя 22, 2023 5:55 pm

Возможно существует решение проще, но я его не знаю, в учебниках ничего на эту тему не нашел.
К моему приложению как вэб-серверу делается POST запрос с несколькими параметрами, но при этом используется Content-Type какой угодно, но только не нужный согласно http протоколу. Spring работает по стандарту и чтобы заработала аннотация @RequestParam для параметра при использовании метода POST, а в теле сообщения от клиента параметры бы передавались просто "param1=val$param2=val2", необходимо чтобы Content-Type был строго "application/x-www-form-urlencoded" (см. стандарт http, передача параметров формы методом POST).
Тут есть 2 пути: или переделывать все клиенты, что мало реально в действующей крупной системе, или научить сервер работать с тем, что есть.
Я хотел чтобы Spring сам мне разобрал параметры, потому подумал, а можно ли перехватить запрос клиента и изменить Content-Type, до того как до него доберется Spring. Оказалось, что до Request`а добраться можно, вернее до HttpServletRequest, у которого есть приватное свойство request, из которого есть только Геттеры его свойств, а мне-то надо его установить, а не узнать. В общем пришлось пойти на ЗЛО, нарушающее принципы инкапсулирования.

В классе конфигурирования внедрим "внедрятор" :)

Код: Выделить всё

   @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(new MyInterceptor());
    }


Который конечно надо создать, т.е. создаем класс MyInterceptor:

Код: Выделить всё

public class MyInterceptor extends WebRequestHandlerInterceptorAdapter{
    public MyInterceptor(){
        super(new WebRequestInterceptor() {
            @Override
            public void preHandle(WebRequest request) throws Exception {

            }

            @Override
            public void postHandle(WebRequest request, ModelMap model) throws Exception {

            }

            @Override
            public void afterCompletion(WebRequest request, Exception ex) throws Exception {

            }
        });
    }
    @Override
    public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception
    {
        try {
            // Костыль!!! Тут ожидаем любым текстом данные формы, для чего нужен верный ContentType
            // Что не сделаешь для общего функционала ¯\_(ツ)_/¯
            if(requestServlet.getRequestURI().equals("/My/Path/Request") && requestServlet.getMethod().equals("POST")) {
                Field requestField = requestServlet.getClass().getDeclaredField("request");
                requestField.setAccessible(true);
                Request requestValue = (Request) requestField.get(requestServlet);
                requestValue.setContentType("application/x-www-form-urlencoded");
            }
        }catch(Exception e){
            return true;
        }
        return true;
    }
}

В конструкторе должен быть обязательно вызван конструктор super. А нас интересует preHandle. Кстати, пока писал это подумал, что свой класс можно было не создавать, а воткнуть все тоже самое в конструктор WebRequestHandlerInterceptorAdapter, скормив именно его addInterceptor, а в него уже сразу внедрить что надо - попробуйте :) Я нашел именно такой пример...

В общем, проверяем наш URL и что метод POST, это надо чтобы у нас меньше сюрпризов было, а то потом будем долго искать, что у нас с Content-Type происходит в других запросах, и собственно вытаскиваем приватный объект и правим его свойство.

Вот такой костыль. Запомните: ТАК ДЕЛАТЬ НЕ НАДО, но если хочется, то можно :)

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Список jar файлов

Сообщение dtvims » Ср ноя 22, 2023 5:57 pm

Ввиду того, что я на 100% не уверен, что выше указал весь набор библиотек, необходимый для работы всех вышеозначеных функций, то приведу полный список используемых библиотек у меня. Я изрядно почистил проект и оставил только нужное, хотя, возможно из Spring библиотек одну или 2 можно убрать, не нарушая работоспособности, т.к. иногда подключал почти наугад и мог забыть что-то удалить.
На всякий случай вот полный список с которым все вышесказанное точно работает (Java использовалась 17-й версии, среда IntelliJ IDEA, режим Java установлен как 1.8 или просто 8):

Код: Выделить всё

activation-1.1.1.jar
commons-codec-1.15.jar
commons-lang-2.6.jar
commons-logging-1.2.jar
ecj-4.6.1.jar
feign-core-12.4.jar
feign-form-3.8.0.jar
feign-form-spring-3.8.0.jar
feign-slf4j-12.4.jar
httpclient-4.5.14.jar
httpcore-4.4.16.jar
jackson-annotations-2.15.3.jar
jackson-core-2.15.3.jar
jackson-databind-2.15.3.jar
jakarta.annotation-api-2.1.1.jar
jdom2-2.0.6.1.jar
jstl-1.2.jar
log4j-1.2.17.jar
log4j-api-2.21.1.jar
log4j-core-2.21.1.jar
lombok-1.18.30.jar
micrometer-commons-1.11.5.jar
micrometer-observation-1.11.5.jar
slf4j-api-2.0.9.jar
slf4j-nop-2.0.7.jar
snakeyaml-1.26.jar
spring-aop-6.0.13.jar
spring-beans-6.0.13.jar
spring-boot-3.1.5.jar
spring-boot-autoconfigure-3.1.5.jar
spring-boot-starter-3.1.5.jar
spring-boot-starter-tomcat-3.1.5.jar
spring-boot-starter-web-3.1.5.jar
spring-cloud-commons-4.0.4.jar
spring-cloud-context-4.0.4.jar
spring-cloud-openfeign-core-4.0.4.jar
spring-context-6.0.13.jar
spring-core-6.0.13.jar
spring-expression-6.0.13.jar
spring-web-6.0.13.jar
spring-webmvc-6.0.13.jar
tomcat-embed-core-10.1.15.jar
tomcat-embed-el-10.1.15.jar
tomcat-embed-jasper-10.1.11.jar
tomcat-embed-websocket-10.1.15.jar

dtvims
Site Admin
Сообщения: 134
Зарегистрирован: Пн авг 02, 2010 2:43 pm

Как изменить к "src/main/webapp/WEB-INF" и можно ли его взять из jar?

Сообщение dtvims » Пн ноя 27, 2023 4:27 pm

У нас 2 вопроса:
1. Как изменить к "src/main/webapp/WEB-INF"?
Этот вопрос самый простой. В классе конфигурации @Configuration добавляем:

Код: Выделить всё

   @Bean
    public TomcatContextCustomizer docBaseCustomizer(){
        return new TomcatContextCustomizer() {
            @Override
            public void customize(Context context) {
                File root = new File("webapp");
                if(root.exists() && root.isDirectory()){
                    LOGGER.info("Set DocBase = " + root.getAbsolutePath());
                    context.setDocBase(root.getAbsolutePath());
                }

                context.setReloadable(true);
            }
        };
    }


Данный пример меняет папку "src/main/webapp" на просто "webapp", если она существует.
Не сложно заметить, что к в коде проверяется существование директории, что говорит о том, что в корне, от куда запускается приложение должна быть папка "webapp", в противном случае должна существовать папка "src/main/webapp".
Если приложение скомпилировано в jar файл, то, чтобы работали JSP скрипты, необходимо физически рядом создать указанные папки и положить в них jsp файлы. Из jar файла, даже если они там есть, эти файлы браться не будут.

2. Как папку webapp взять из jar файла?
Согласно основному мнению - НИКАК! А все потому, что когда стартует томкат, он проверяет, чтобы DocBase был или физической папкой и war файлом (именно war файлом и более ничего). Т.е. обмануть tomcat вроде бы никак нельзя.
Хотя, если очень хочется, то можно. Есть 2 пути обхода хотя они сводятся к одному.
Сперва надо в нашем проекте создать свою папку "webapp" (можно назвать как угодно - это не принципиально) и пометить ее как resource (как это сделать, см. инструкции к своей среде разработки).
Далее необходимо в томкат внедрить путь к нашему jar любым способом. Да, именно к jar файлу, путь внутри будет определен именно по метке(ам) resource. Можно было бы попробовать подтянуть полный путь, в том числе и внутри jar, но там так не работает.
Внедрить путь источник можно переопределив класс StandardRoot из томката, что делает сам spring boot, который переопределяет его на класс LoaderHidingResourceRoot. Фактически, можно переопределить LoaderHidingResourceRoot, т.е. расширить его и переопределить функцию WebResourceSet, где установить jar источник. Далее в tomcat.Сontext необходимо через setResources(StandardRoot) установить наш объект, с ресурсами из jar.
Тут есть одна проблема, что tomcat не должен быть запущен, в противном случае будет ошибка, но при запуске, вероятно как раз и будет переопределен StandardRoot на собственный от Spring boot. Потому тут важно правильно выбрать место, где его можно поменять, чего я не нашел, а точнее и не искал...
Другой вариант, что можно менять на горячую, но не сам объект StandardRoot, а в него добавить нужный нам ресурс. Но чтобы в него что-то добавить, он должен быть уже создан. Я для себя решил это сделать там же , где включаю JSP.
В итоге получаем следующее:

Код: Выделить всё

import org.apache.catalina.Context;
...

   public Context tomcatContext = null;
   
   @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        try {
            WebResourceRoot wr = tomcatContext.getResources();
            URL mainClass = MainApps.class.getResource("/" + MainApps.class.getName().replace('.', '/') + ".class");
            if (mainClass.getProtocol().equals("jar")) {
                String file = mainClass.getFile();
                int fI = file.indexOf('!');
                if (fI > 0){
                    file = file.substring(0, fI);
                }
                mainClass = new URL(file);
                String resPath = (new File(mainClass.toURI())).getCanonicalFile().getPath();
                wr.addJarResources(new JarResourceSet(wr, "/", resPath, "/"));

            }
        }catch (Exception ex){
            LOGGER.error(ex, ex);
        }
        registry.jsp();
    }

   @Bean
    public TomcatContextCustomizer docBaseCustomizer(){
        return new TomcatContextCustomizer() {
            @Override
            public void customize(Context context) {
                File root = new File("webapp");
                if(root.exists() && root.isDirectory()){
                    LOGGER.info("Set DocBase = " + root.getAbsolutePath());
                    context.setDocBase(root.getAbsolutePath());
                }

                context.setReloadable(true);
                tomcatContext = context;
            }
        };
    }


Пришлось создать указатель tomcatContext иначе его взять было бы не откуда, т.е. мы его заполняем из TomcatContextCustomizer, ну а далее обновляем в configureViewResolvers. Каждая функция вызывается один раз, что нам в общем-то и нужно.
По хорошему проверить бы еще что tomcatContext не null, но если мы все сделали правильно, то он не должен быть null. Вообще тут уже все зависит от работы вашего приложения, на сколько у Вас критично от куда что берется...
В принципе я оставил процедуру, для смены DocBase, хотя она не работает, т.к. у меня такой папки нет в рабочей инсталяции, но эта папка есть в проекте, а ведь до компиляции, в среде - это именно физическая папка. Таким образом при разработке работает физическая папка, а после компиляции, эта папка попадает в jar и уже работает из jar файла.

(С) И невозможное возможно!


Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 10 гостей