Оживление двадцатилетнего (20) Java-проекта
В современном окружении. Рассказываю что делать когда проект старше твоей подружки.
В подвале морга твоего
Двадцать лет (20) для любого программного проекта это последняя стадия разложения очень и очень много.
Поэтому если вы попробуете отправить запрос на поддержку столь старого проекта компаниям которые занимаются разработкой ПО — в большинстве случаев просто получите отказ и предложение обратиться к психиатру.
А редкие единицы, действительно готовые за такое взяться неприятно удивят ценой вопроса. Причем без гарантий и необходимых компетенций.
На это есть серьезные причины, в первую очередь кадровые:
Сильно устаревший проект — первая причина (после зарплаты), по которой разработчики пойдут массово увольняться или просить перевода в другое место.
Бесконечная погоня за «хайповыми» строчками в резюме привела к тому, что ныне разработчики вообще стараются обходить любые устаревшие технологии стороной, не то что обладают необходимыми навыками для работы с таким проектом.
Словом, не мучайте ваших «бедных зумеров» от разработки — если у вас есть legacy-проекты времен молодости В. Цоя, которым нужен «ремонт и обслуживание» — пишите лучше нам, мы поможем.
Ниже будет детальный рассказ с картинками о процессе оживления столь древнего проекта и его запуска в ОС 2024 года, с описанием всех шагов и вносимых изменений.
Тестовый проект
Ввиду размеров, сложности и подписанных бумаг о неразглашении — весь процесс будет показан не на реальном проекте, а на тестовом, который сильно меньше и проще.
Этот тестовый проект использовался нашей командой для обкатки технологийнекромантиии подходов к оживлению, перед тем как мы взялись за что-то большое.
В качестве поциента для опытов была взята очень старая версия AppFuse, от 2004го года:
Appfuse — ныне устаревший и заброшенный шаблон (boilerplate) для проектов на Java, предок JHipster от знаменитого Matt Raible (Java Champion, на минуточку).
Последние крупицы знаний об этой замечательной штуке можно найти вот тут.
AppFuse по своей структуре — классическая «трезвенка» (трехзвенная архитектура) с четким разделением на слои:
слой работы с данными (DAO), сервисный и слой представления.
Вот что внутри выбранной нами версии из 2004го года:
Hibernate 2.1.3 Struts Menu 2.0 Velocity 1.4 Spring 1.0.2 Jakarta Struts 20031202 - да да, еще до переименования.
Все это когда-то собиралось c помощью Apache Ant 1.6.x и Java SE 1.4 а затем работало на Apache Tomcat 4.x.
Ниже в статье будет показана и сборка и запуск столь старой версии в современном окружении — в качестве важного первого шага.
Поскольку довольно часто задача заключается лишь в самой возможности запуска столь старого проекта на современной ОС и внесении минимальных правок, без глобальных переделок.
Немного про AppFuse
На самом деле все несколько сложнее и в AppFuse даже столь старой версии реализована адаптивность — например можно переключить настройками используемый веб-фреймворк со Struts на Spring MVC, есть поддержка разных языков локализации и разных СУБД.
Чтобы не раздувать и без того объемную статью, мы не будем описывать здесь абсолютно весь функционал AppFuse, сфокусируемся только на самом процессе портирования и применяемых методологиях.
Надо заметить что AppFuse был хорошим примером сложного проекта на Java, на котором можно было подсмотреть правильную организацию и связывание различных сложных фреймворков в единое приложение.
Важной «фичей» AppFuse была его «кроссплатформенность» — возможность работы под разными серверами приложений.
В те далекие годы большинство веб-проектов на Java разрабатывались и работали на каком-то одном сервере приложений (например IBM Websphere) в течение всего своего жизненного цикла и AppFuse на 2004й год — единственное (известное нам) исключение с поддержкой разных серверов приложений.
Личный опыт
Мы использовали AppFuse для коммерческой разработки с 2009 года и до самых его последних дней — до появления проекта JHipster и перехода в него основателя Matt Raible.
Ниже для иллюстрации несколько скриншотов одного из проектов на базе AppFuse от 2010го года — системы централизованного сбора информации о сетевых маршрутах и их изменениях.
Когда-то давно эта система использовалась для отслеживания изменений в структуре сети и быстрого на них реагирования.
Другими словами, у нас есть серьезный опыт работы с AppFuse, который позволяет в разумные сроки провести миграцию и обновление проекта на его основе — пишите, поможем.
Но вернемся к описанию процесса оживления, попробуем собрать и запустить оригинал из 2004го года.
Оживление
В ныне архивном репозитории AppFuse когда-то велась вся разработка проекта, прекратилась она только в районе 2018 года — совсем недавно.
По этой причине в репозитории много разных веток и тегов, не имеющих отношения к нашей задаче миграции. Чтобы не выкачивать все это устаревшее говно — стоит использовать кнопку «Download ZIP» в интерфейсе Github:
По нажатию будет сформирован ZIP-архив с выбранной веткой, скачивание которого запустится автоматически.
Распаковываем и смотрим внутрь.
А внутри как обычно говно ряд недоделок, характерных для старых проектов использующих для сборки Apache Ant, из-за которых собрать проект сразу и без проблем не получится.
Напоминаю для самых юных читателей:
Apache Ant по-умолчанию не поддерживает управление зависимыми библиотеками и их автоматическое скачивание.
Никаких <dependency></dependency>, «Maven Central» и mvn install тогда не было, все эти радости появились позже и в другой системе сборки — в Apache Maven, который и занял всю нишу систем сборки для Java, вытеснив Ant.
Такая особенность Ant означает необходимость хранения всех используемых библиотек рядом с исходным кодом, чаще всего в репозитории.
А для публичных и популярных проектов вроде AppFuse подобное хранение означает проблемы с лицензированием — часть библиотек выкладывать в собранном виде запрещают их авторы.
XDoclet
Именно так и получилось с фреймворком XDoclet, который сам по-себе достаточно интересный, хотя и давно не актуальный:
XDoclet is an open source code generation engine. It enables Attribute-Oriented Programming for java. In short, this means that you can add more significance to your code by adding meta data (attributes) to your java sources. This is done in special JavaDoc tags.
Да, вы правильно поняли — это реализация аннотаций для Java 1.4, до их официальной поддержки в 1.5 и всего последующего развития.
/** * @struts.validator type="required" * @struts.validator type="email" */ public void setUsername( String username ) { this.username = username; }
Что позже превратилось в широко известные современным разработчикам @NotNull и @Email (из JSR303).
Но в AppFuse используется и другая фича этого фреймворка:
XDoclet will parse your source files and generate many artifacts such as XML descriptors and/or source code from it. These files are generated from templates that use the information provided in the source code and its JavaDoc tags.
Генерация исходного кода во время сборки — оказывается оно работало еще в 2004 м году!
Именно этот процесс и нужно будет оживить для работы устаревшей версии.
Для начала скачаем архив xdoclet-bin-1.2.3.zip с сайта SourceForge и распакуем в каталог ./lib нашего старого проекта на AppFuse.
Должно получиться что-то такое:
Исправляем номер версии XDoclet в файле ./lib/lib.properties:
Apache Ant
Пришло время скачать и установить эту утилиту, чтобы запустить наконец сборку проекта.
Можно использовать как последнюю доступную версию с поддержкой Java 1.8 так и 1.6.5 из 2005го года (да большего антуража) — обе будут работать в современном окружении.
Но перед использованием необходимо выполнить еще один важный шаг: скопировать библиотеку JUnit версии 3.8 в каталог lib в каталоге Apache Ant.
Это тоже характерно для старых проектов, использующих Apache Ant — для использования юнит-тестов когда-то приходилось подкладывать внешние библиотеки в ресурсы утилиты сборки.
Старая Java
Тут сразу нужно пояснить один важный момент:
полностью раскрыть проблематику работы старых версий JDK/JRE в современном окружении в рамках этой статьи не получится.
Будут пропущены такие замечательные вещи как развертывание устаревших версий ОС в эмуляции, ради организации сборки с помощью устаревших же (и неподдерживаемых) версий JDK или сама сборка устаревших версий JDK из исходников — просто чтобы не раздувать повествование на три тома.
Мы пойдем более коротким путем:
используем последние остатки обратной совместимости в JDK 1.8, которая поддерживается и доступна в любой современной ОС.
И рантайм (JRE) и JDK для версии 1.8 присутствует во всех дистрибутивах Linux, *BSD, в MacOS и Windows — везде и врядли это изменится в ближайшее время.
Еще JDK 1.8 до сих пор позволяет компилировать код на Java начиная с первых версий и при этом позволяет указывать 1.4 версию в качестве целевой платформы.
Вы увидите предупреждение при такой сборке, но проект соберется успешно.
Более современные версии JDK, начиная с 9й — не позволяют собирать для Java 1.4.
Повторюсь что это работает далеко не всегда:
в те времена были сторонние реализации виртуальных машин Java, каждая со своими особенностями, а также активно использовалось внутреннее недокументированное API — классы «com.sun.*», на которые не распространялась необходимость обратной совместимости.
Один из примеров использования такого API как раз ломает первую сборку нашего древнего проекта:
К счастью это всего-лишь забытый автором и неиспользуемый import, который можно просто удалить:
После этой маленькой правки должна успешно отрабатывать сборка проекта, которую я опишу чуть ниже.
База данных
Наверное самое больное место устаревших проектов, поскольку за 20 лет СУБД изменились до неузнаваемости и старые интерфейсы подключения (JDBC-драйвера в случае Java) работать не будут.
У AppFuse есть поддержка нескольких разных баз данных, в том числе «локального» файлового Firebird — достаточно было просто переключить используемую СУБД на него и пропустить эту проблему.
Но для большей наглядности мы решили показать все шаги на «нормальной базе данных» — MySQL (ныне MariaDb).
Попытка использования JDBC-драйвера (еще для MySQL), присутствующего в каталоге ./lib нашего древнего проекта сразу же выдала ошибку протокола, поэтому драйвер пришлось немедленно обновлять.
Пропустим шаги установки самого сервера MariaDb/MySQL — иструкций для этого в интернете достаточно, ниже только необходимое для работы AppFuse:
- Локальное подключение через сетевой адрес;
- Пользователь root с полными правами;
- Пользователь mysql с правами на базу appfuse.
Для проверки подключения необходимо убедиться что команда:
mysql -u root -h localhost -p
Отрабатывает правильно, причем от того пользователя под которым вы запускаете сборку.
JDBC драйвер
Ныне эта библиотека называется MariaDB Connector/J, скачать можно по этой ссылке, для статьи я использовал версию 2.7.11.
Скачиваете и копируете .jar файл в каталог ./lib внутри проекта AppFuse:
После чего необходимо изменить параметр mysql.jar в файле ./lib/lib.properties:
Вписав полное имя cкачанного .jar файла.
Но к сожалению этого мало, поскольку после форка и переименования MySQL в MariaDB было изменено еще и имя класса JDBC-драйвера.
com.mysql.jdbc.Driver
org.mariadb.jdbc.Driver
Заменить название необходимо в файле properties.xml в корне проекта AppFuse:
Там же задаются параметры для админского подключения к серверу баз данных — для создания структуры таблиц.
Если все настроено правильно, должна отрабатывать команда:
ant setup-db
Которая создаст базу данных appfuse и все необходимые таблицы внутри.
Дальше можно выполнить юнит-тест для проверки слоя DAO:
ant test-dao -Dtestcase=UserDAO
Если все настроено верно то никаких ошибок при работе не будет.
Переходим на следующий уровень.
Tomcat
На самом деле я вас немного на#бал ввел в заблуждение — команды выше не выполнятся, поскольку не был учтен еще один важный момент:
скрипт сборки внезапно использует классы Apache Tomcat, без которых сборка не работает.
Вот такой я нехороший человек да.
Скачиваем архив со сборкой Apache Tomcat времен восстания Колчака, вот отсюда, вам нужен файл apache-tomcat-4.1.32.zip.
Распаковываем и задаем переменную среды CATALINA_HOME:
export CATALINA_HOME=/полный/путь/до/apache-tomcat-4.1.32
После этого будут отрабатывать скрипты выше, также можно будет запустить развертывание приложения командой:
ant setup
При запуске собранное приложение будет скопировано в каталог $CATALINA_HOME/webapps, также будет скопирован .jar-файл с JDBC-драйвером в $CATALINA_HOME/common/lib.
Теперь можно запускать сам Tomcat с нашим legacy-приложением на борту:
$CATALINA_HOME/bin/startup.sh
Лог с ошибками будет в том же месте что и у современных версий томката:
$CATALINA_HOME/logs/catalina.out
Некоторые вещи никогда не меняются, ага.
Увидеть запущенное приложение можно по адресу:
http://localhost:8080/appfuse/login.jsp
Стартовая страница выглядит вот так:
На этом первая часть завершается, а у нас на руках есть оживший труп из далекого прошлого, вполне себе работающий.
На самом деле статья писалась с более глобальной задумкой — показать полную миграцию такого проекта, с полным обновлением библиотек и обновлением логики.
К сожалению времени просто нехватило, поэтому ниже лишь первая часть этого процесса, но даже по ней виден масштаб и сложность такой работы. Как будет время — обязательно допишу статью.
Миграция
Опишу для начала чего именно мы добьемся подобной миграцией и что получим в результате.
Задача миграции legacy-проектов обычно строго утилитарная:
добиться возможности сборки и запуска под актуальными версиями JDK, обновив все используемые библиотеки до последних версий.
К сожалению поддержание жизни в таком проекте считается вынужденной мерой, а не каким-то невероятным достижением, поэтому мало кому интересно например обновление интерфейса в legacy-проекте или его кардинальная переработка:
добавление Bootstrap, SPA с React/Angular и так далее.
Смыслом подобной миграции является снижение накладных расходов по эксплуатации проекта, ради чего и делается переход на свежие версии JDK, а также закрытие дыр в безопасности — путем обновления всех используемых библиотек.
Cборка проекта будет осуществляться с помощью Apache Maven, общая структура проекта сохранится, также как и страницы на JSP.
Разумеется можно было пойти еще дальше:
полностью отказаться от Struts, заменив его на Spring MVC и переверстать все JSP-страницы на Thymeleaf
Размеры AppFuse это бы позволили.
Но мы хотели сохранить реалии большого legacy-проекта, где главным препятствием к таким действиям является как раз сложность и объем кода интерфейсной части.
Именно по этой причине обновить слой работы с СУБД в старом проекте многократно проще чем сменить используемый MVC-фреймворк.
Прототип
Первым шагом миграции является создание технического прототипа — отдельного приложения, где будут собраны интересующие нас технологии и фреймворки нужных версий, но без всего объема кода из основной системы.
Основой проекта является связка Struts + Spring + Hibernate, упакованная в WAR-приложение с возможностью запуска в Apache Tomcat — вот ее и будем собирать.
Можно использовать «Maven Archetype» — генератор проектов, который создаст начальную структуру и все базовые классы.
Проект Apache Struts предоставляет несколько таких генераторов, самый «жирный» запускается вот так:
mvn archetype:generate -B -DgroupId=com.mycompany.mysystem \ -DartifactId=myWebApp \ -DarchetypeGroupId=org.apache.struts \ -DarchetypeArtifactId=struts2-archetype-starter
К сожалению он даст лишь скелет WAR-приложения, интеграцию со Struts, Spring и тестовую страницу — этого слишком мало, поскольку добавление Hibernate это существенная часть работы.
Поэтому есть альтернативный вариант:
использовать готовый шаблон проекта в виде CRUD-приложения с доведением до нужной кондиции.
Поскольку связка Struts + Spring + Hibernate очень популярная — таких проектов масса.
Мы взяли за основу вот такой, достаточно популярный среди такого рода проектов. Шаблон к сожалению уже сильно устарел, поэтому первым делом пришлось все обновлять до требуемых версий.
Шаг первый: подготовка прототипа
Оригинальная версия шаблона, взятого за основу была создана 13 лет назад, очевидно что все внутренности сильно устарели.
Помимо устаревания, в шаблоне используется полностью ручное связывание бинов Spring через XML-файлы настройки, а также ручная работа с Hibernate (без аннотаций), что сильно затруднит жизнь в большом проекте.
Для большого legacy-проекта наверное стоило бы оставить полностью ручную настройку с использованием XML, поскольку она легче отлаживается, но мы все же хотели показать более «молодежный» результат, поэтому пошли на компромисс с совестью:
- Аннотации используются для сущностей JPA, репозиториев и сервисного слоя;
- Action-классы Struts и общая настройка веб-приложения в web.xml остаются в старом стиле, в виде xml-файлов.
Полностью готовый прототип выложен на Github, вот тут.
Ниже несколько важных деталей о процессе обновления.
Spring и Hibernate
Помимо обновления версий этих фреймворков, пришлось переделывать логику их связывания и взаимодействия между собой.
Вся настройка находится в файле src/main/resources/applicationContext.xml, без разделения на отдельные секции, как и в оригинальном проекте.
Первым делом были убраны версии используемых XSD:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> ..
Это было сделано специально, чтобы использовались последние доступные версии XSD-схем, поставляемые с самими библиотеками Spring.
Затем было добавлено сканирование классов:
<context:component-scan base-package="com.Ox08.samples.migration.step1.crudapp" />
Поскольку часть настройки теперь находится в аннотациях классов.
Был обновлен бин «PropertyConfigurer», отвечающий за чтение параметров конфигурации и подстановку их в окружение:
<bean id="propertyConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"/> </bean>
Из-за того что оригинальный класс:
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
помечен как deprecated в последних версиях Spring и не рекомендован к использованию.
В качестве замены для устаревшего C3P0 (который использовался в оригинальной версии), была добавлена настройка пула подключений Hikari, который является реализацией по-умолчанию для современных проектов на Spring:
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig"> <property name="dataSourceProperties" > <props> <prop key="url">${db.url}</prop> <prop key="user">${db.username}</prop> <prop key="password">${db.password}</prop> </props> </property> <property name="connectionTestQuery" value="SELECT 1" /> <property name="dataSourceClassName" value="${db.driverClassName}" /> </bean> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" > <constructor-arg ref="hikariConfig" /> </bean>
Наконец самое главное: вместо использования устаревшего sessionFactory я включил настройку JPA entityManager:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="com.Ox08.samples.migration.step1.crudapp.model" /> <property name="persistenceProviderClass" value="org.hibernate.jpa.HibernatePersistenceProvider" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> <property name="jpaPropertyMap"> <map> <entry key="hibernate.hbm2ddl.auto" value="update"/> </map> </property> </bean> <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean>
Также было добавлено сканирование @Repository аннотаций и включение бинов Spring Data:
<jpa:repositories base-package="com.Ox08.samples.migration.step1.crudapp.repo" />
Struts и Sitemesh
Теперь о грустном и печальном:
- Текущая версия Struts 6.x не поддерживает новое Jakarta EE (после переименования JEE), поэтому пришлось брать тестовую 7ю и собирать ее из исходников.
- Apache Tiles, использующийся в проекте 2004го года — мертв, его разработка давно остановлена. Поэтому его пришлось заменить на Sitemesh.
В шаблоне проекта, который мы взяли за основу Sitemesh не использовался, поэтому был добавлен с минимальной настойкой.
Файл src/main/webapp/WEB-INF/decorators.xml:
<?xml version="1.0" encoding="ISO-8859-1"?> <decorators defaultdir="/WEB-INF/decorators"> <!-- Any urls that are excluded will never be decorated by Sitemesh --> <excludes> <pattern>/nodecorate/*</pattern> <pattern>/styles/*</pattern> <pattern>/scripts/*</pattern> <pattern>/images/*</pattern> <pattern>/struts/*</pattern> </excludes> <decorator name="main" page="main.jsp"> <pattern>/*</pattern> </decorator> </decorators>
Как видите используется единственный шаблон для всех страниц (main.jsp).
web.xml
В главном настроечном файле веб-приложения тоже пришлось многое менять. Для начала были изменены используемые namespaces и версия XSD:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0"> ..
Это указание на использование последней версии спецификации Servlet API.
Затем были переделаны фильтры Struts:
<filter> <filter-name>struts-prepare</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareFilter</filter-class> </filter> <filter> <filter-name>struts-execute</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsExecuteFilter</filter-class> </filter>
Замена была необходима, поскольку используемый в оригинале фильтр:
org.apache.struts2.dispatcher.ActionContextCleanUp
в новой версии Struts является устаревшим и более не используется.
Также был добавлен фильтр для SiteMesh:
<filter> <filter-name>sitemesh</filter-name> <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class> </filter>
OpenSessionInViewFilter
был удален, поскольку использовался для автоматического открытия сессии Hibernate на каждый запрос, что всегда являлось плохой практикой.
Maven
В скрипте сборки были обновлены версии используемых библиотек, некоторые из которых успели поменять именование.
<groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId>
<groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId>
Также был добавлен общий артефакт для всего Jakarta EE API:
<dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-web-api</artifactId> <version>${jakartaEEVersion}</version> <scope>provided</scope> </dependency>
В оригинальном проекте использовались отдельные артефакты для частей EE API (Servlet API).
Также был обновлен плагин для Jetty:
<plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>11.0.18</version> <configuration> <webApp> <contextPath>/${project.artifactId}</contextPath> </webApp> </configuration> </plugin>
На этом формирование прототипа завершено и можно приступать к переносу логики.
Шаг второй: перенос логики
Смысл в том чтобы аккуратно и внимательно перенести логику из устаревшего проекта 2004го года в подготовленный выше прототип, с одновременным обновлением ее до современных реалий.
И это будет нихрена не просто.
Надо понимать, что множество элементов современного приложения на Java в 2004 м году еще не являлись стандартными и реализовывались «кто во что горазд» — конвертеры для денежных единиц и дат, валидация входных данных и так далее.
Все места в проекте, использующие подобную логику придется переделывать и заменять реализацию на современные аналоги.
Сборка проекта и XDoclet
Напомню что старый проект 2004года собирается с помощью Ant и использует несколько технологий, ныне полностью устаревших.
В первую очередь это XDoclet и все что его касается:
специальные метки в исходниках генерация специальных классов при сборке.
Проект XDoclet прекратил свое развитие в 2005 м году, поэтом оживлять и тащить его в 2024й — крайне проблематично, хотя все еще технически возможно, ну вы поняли кому писать.
Однако размеры тестового проекта вполне позволяют избавиться от этой технологии, просто перенеся сгенерированные исходники из старой сборки.
Каталог ./build/web/gen в AppFuse содержит исходники, сгенеренные с помощью XDoclet:
Вот так выглядит код одного из сгенеренных классов:
package org.appfuse.webapp.form; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionErrors; /** * Generated by XDoclet/actionform. This class can be further processed with XDoclet/webdoclet/strutsconfigxml and XDoclet/webdoclet/strutsvalidationxml. * * @struts.form name="roleForm" */ public class RoleForm extends BaseForm implements java.io.Serializable { protected String name; protected String description; /** Default empty constructor. */ public RoleForm() {} public String getName() { return this.name; } /** * @struts.validator type="required" */ public void setName( String name ) { this.name = name; } public String getDescription() { return this.description; } /** * @struts.validator type="required" */ public void setDescription( String description ) { this.description = description; } /** * @see org.apache.struts.action.ActionForm#reset(org.apache.struts.action.ActionMapping, * javax.servlet.http.HttpServletRequest) */ public void reset(ActionMapping mapping, HttpServletRequest request) { // reset any boolean data types to false } }
Минутка матчасти
Полагаю, вы задаетесь вопросом
Дело в том что AppFuse это еще и настраиваемый движок для быстрой разработки бизнес-логики, одной из ключевых фич которого является «сквозная» генерация исходного кода по модели данных.
В случае столь старой версии, генерируется небольшая часть — только классы форм Struts с правилами валидации, описанными в модели.
Но в более поздних версиях, генерировалось куда больше частей проекта.
Для примера, вот так выглядит модель роли (фрагмент):
package org.appfuse.model; import java.io.Serializable; /** * This class is used to represent available roles in the database.</p> * * <p><a href="Role.java.html"><i>View Source</i></a></p> * * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a> * @version $Revision: 1.3 $ $Date: 2004/05/16 02:16:44 $ * * @struts.form include-all="true" extends="BaseForm" * @hibernate.class table="role" */ public class Role extends BaseObject implements Serializable { //~ Instance fields ======================================================== .. }
Стартовый для процесса генерации специальный тег в описании:
* @struts.form include-all="true" extends="BaseForm"
во время сборки порождает наследование при генерации класса формы:
/** * Generated by XDoclet/actionform. This class can be further processed with XDoclet/webdoclet/strutsconfigxml and XDoclet/webdoclet/strutsvalidationxml. * * @struts.form name="roleForm" */ public class RoleForm extends BaseForm implements java.io.Serializable { ..
Вообще это достаточно специфичная технология и в «большом legacy», подобная генерация чаще всего будет происходить по XSD-схемам, с добавленными вручную правилами.
С конца 90х и до появления JSON, связка из технологий XML, XSD и XSLT была очень популярной и часто использовалась для генерации классов.
Словом, генерация исходного кода по XSD-схемам и метаданным — распространенный подход в больших проектах, скорее всего вы встретите подобное еще не раз и не два.
Самым легким решением для избавления от подобной генерации является добавление в проект ее результата — сгенерированных исходников и их последующее статичное использование, что мы и сделаем в нашем тестовом проекте.
Maven
Разумеется перевод большого проекта с одной системы сборки на другую —непростая задача даже без всякого «legacy».
В случае же столь серьезного устаревания (20 лет), задача вообще превращается в неподъемную для неподготовленного разработчика.
Напомню что использовалось в нашем маленьком тестовом проекте:
старая версия самой системы сборки, устаревшие модули расширения к ней же, внешние библиотеки, которые надо отдельно ставить и переменные окружения
В случае большого legacy-проекта, умножайте все эти проблемы на 10, особенно если проект был сугубо внутренний и не вся последовательность сборки была автоматизирована.
Поэтому пожалуйста не мучайте ваших штатных быдлокодеров разработчиков на окладе подобными задачами — отдайте их нам, мы опытные в этом деле.
Это очень распространенное заблуждение считать починку сборки и тем более ее миграцию чем-то вроде уборки отхожего места в армии — работой для «черпаков» от программирования.
Полный исходный код прототипа первой части выложен на Github, наслаждайтесь.
В следующей части я расскажу в деталях про миграцию отдельных фреймворков: