Устаревшие приложения Java Servlet
С момента своего запуска в 1997 году Java-сервлеты стали основным выбором для разработки корпоративных приложений. Sun быстро добавила JSP, библиотеки тегов и JSF. Сообщество открытого исходного кода заставило работать много веб-фреймворков. Struts 1.0 был первым веб-фреймворком, получившим большое распространение в 2000 году, и, возможно, самым ранним веб-фреймворком Java, с которым вы все еще сталкиваетесь сегодня на предприятии. Если он все еще присутствует в ваших развернутых приложениях, он определенно унаследован, так как последний выпуск был в 2008 году, а в апреле этого года он официально закончился. Кто-то всегда собирается сказать переписать, а иногда переписывает окупается.
Вклад в проблемы для крупных корпоративных систем:
Классические синглтоны против инъекций зависимостей
В то время как в небольших кругах было известно, что решения, опирающиеся на Singletons (шаблон проектирования, а не идиома Spring и т. Д.), Постепенно станут неосуществимыми, подавляющее большинство профессиональных разработчиков в 2000 году в то время не знали об этом. Внедрение Dependency Injection достигло больших успехов в 2004 году благодаря записи в блоге Мартина, а в 2006 году XML-метод Spring Framework по декомпозиции веб-приложений Dependency Injection стал основным. Само по себе это имело проблемы, но давайте отметим 2006 год как поворотный момент для большинства.
С тех пор продолжается дискуссия о том, нужен ли контейнер для внедрения зависимостей (я тоже создал один — PicoContainer), или достаточно ли семантики инъецируемых классов / компонентов. Также с тех пор появляются выпускники в области программирования, которые не слышали о Внедрении Зависимостей (или его основной принцип — Инверсия Управления), постепенно сбрасывая часы. Существует также рост динамических языков (Python, Ruby) и их неконтейнерной реальности (хотя идиомы классов DI все еще применяются в соответствии с Алексом Мартелли и Джимом Вейрихом ).
Другие причины
Помимо проблем «статического состояния» синглетонов, приложения сервлетов Java в современном мире могут также страдать от:
- Слишком много фильтров сервлетов — каждая новая команда ставит один перед всеми остальными
- За использование ThreadLocal для выполнения сложных операций
- Сервисный локатор в миксе, но без планов двигаться дальше к нему или от него
- Второй / третий компонент фреймворков в миксе. Скажем , команда начала с Struts1, а затем положить выигрышном слишком (для разных страниц)
Что такое Удушение снова?
Помните, что удушение означает безопасный постепенный переход от плохого к хорошему, одновременно добавляя новую функциональность и итеративно обновляя часть старого и частично нового приложения.
Это отличается от простого омоложения унаследованной кодовой базы тем, что вы определили, что конечные цели омоложения недостижимы или исходная точка слишком далека.
См. Мои предыдущие записи в блоге: « Обновление устаревших приложений» , «Единый план побега», а также вчерашняя « Удушение приложений»: тематические исследования и окончательная статья Мартина: StranglerApplication.
Ваша новая проблема
После того, как вы решили задушить, а не переписать, ваша новая проблема заключается в том, что вы на самом деле не хотите добавлять больше кода в существующее приложение WAR-файла, потому что это карточный домик, и может рухнуть с любой тонкой ошибкой кодирования. Он хрупкий, и, возможно, его защищают несколько юнит-тестов.
Удушение веб-приложения в целом
Как уже сотни раз реализовывалось в отрасли, удушение веб-приложений предполагает запуск прокси-сервера перед старым и новым приложением и через сопоставления перенаправляет входящие HTTP-запросы либо в старое, либо в новое приложение. Через какое-то время вы добавите больше кода (и функциональности) в новое приложение и больше отдалитесь от старого с соответствующими изменениями в прокси-сервере маршрутизации. Этот прокси может быть программируемым балансировщиком нагрузки типа «большой утюг», как F5. Это также может быть часть с открытым исходным кодом, как HAProxy. Эти два — масштабные способы сделать это. Ближе к коду приложения вы можете просто программировать маршрутизацию на уровне Apache.
Чисто Java-решение
Это не обязательно лучше, но если вы придерживаетесь других стеков развертывания и соображений масштабирования, возможно безопасное кодирование в контейнере сервлета (Tomcat, Jetty и т. Д.). Вот что я собираюсь изложить.
Второй файл WAR.
WAR-файлы — это автономный архив развертывания контейнеров сервлетов. По соглашению тот, кто называется ROOT.war, будет развертываться без контекста, а те, которые названы любым другим способом, предоставляют свое имя файла соответствующему контексту. По крайней мере, по умолчанию это верно для большинства контейнеров веб-приложений.
Для этого решения у нас будет новое приложение WAR-файла, которое займет прежнее место в структуре каталогов для рассматриваемого сайта. Если старое приложение было найдено по адресу example.com/*
, то после внедрения будет новое приложение. То, что было старым приложением, будет удалено из этого корневого контекста. Вот example.com/oldApp/*
в этом примере.
Первоначальная версия этого делегирует все HTTP-запросы к эквивалентному URL-адресу старого приложения. Это так же просто, как добавить oldApp / к пути и выполнить несколько строк магии сервлета (см. Ниже). Один за другим конечные точки переписываются в новом файле войны и устаревают в старом. Может случиться так, что код тоже мигрирует (так как он обновлен), но, независимо от того, происходит это или нет, у вас должен быть высокий уровень тестов в соответствии с тестовой пирамидой, чтобы гарантировать качество. Из них функциональные тесты могут перейти в старое приложение и вернуться обратно, если это имеет смысл.
Старому приложению также необходим новый сервлет-фильтр, запускающий его перед тем, чтобы предотвратить видимость конечных точек с адресов TCP / IP конечного пользователя. Вы все еще можете захотеть, чтобы конечные точки были доступны внутри для отладки.
Один военный файл вызывает конечные точки в другом
Если у вас есть два веб-приложения «a» и «b» (a.war & b.war), сервлеты или фильтры в «a» могут вызывать конечную точку в «b» следующим образом:
HttpServletResponseBuffer tmpResponse = new HttpServletResponseBuffer(); tmpResponse.copyAFewThingsFromTheUpStreamResponse(response); HttpServletRequest servletRequest = (HttpServletRequest) request; ServletContext bContext = servletRequest.getSession().getServletContext().getContext("/b"); RequestDispatcher endPoint = context.getRequestDispatcher("/aStaticOrDynamicResourceOrEndpoint"); MyHttpServletResponse newResponse = new MyHttpServletResponse((HttpServletResponse) response); endPoint.include(request, tmpResponse); // or .forward(..) // unpack payload from tmpResponse, and do things to your stored response.
Сложность в том, что вы можете получить только поток символов / байтов из ‘b’. Вам придется обрабатывать эти потоки так, как если бы они были получены TCP / IP. Это включает в себя разбор, если вы говорите о JSON или XML. Это преднамеренный дизайн контейнера сервлета, в котором приложения сервлета (разные военные файлы) находятся в разных загрузчиках классов, так что они не могут мешать друг другу программно — один и тот же классический синглтон в двух военных файлах на самом деле два совершенно разных экземпляров. Вот представление о загрузчиках классов (красные линии показывают родительские и родительские загрузчики классов):
Они по-прежнему могут мешать друг другу с точки зрения доступности ЦП, поскольку JVM не может защитить одно веб-приложение от загруженности ЦП или сокета другого. Это недостаток реализации Sun, который Oracle может исправить в будущем.
There’s some extra magic required in to allow ‘a’ to invoke ‘b’, that’s not allowed by default. At least for Tomcat, context.xml needs to have an attribute set crossContext="true"
. Here is a Perl one-liner for that, as that’s outside the war file:
perl -p -i.bak -e 's/<Context>/<Context crossContext=\"true\">/g' /path/to/tomcat_install/conf/context.xml
Threading
As at least Tomcat and Jetty have implemented it, no new threads are used when ‘a’ invokes something in ‘b’. This is important as you otherwise worry about a drop in capacity for the part strangled “application” as you put it to production. If the containers were to silently use new thread during include(..)
or forward(..)
, you would see capacity approximately halve assuming you were more or less fully utilized for the machine (or virtual machine).
Shared aspects.
There are lots of things that come down from the browser with a HTTP request. The URL is an obvious one, as is the payload of the request (POSTs have post-fields). There’s also cookies, and some may be pertinent to your new app, with others for the old app. You could choose to separate those if it matters, or let both applications see them.
There’s also a JSESSIONID for Java Servlet applications, used to identify who a request is on behalf of. Sharing that for two apps (old and new) serving the same end-user request is perfectly OK, and like the threading section above means a more streamlined strangulation.
Further reading.
Sitemesh has been using context.getRequestDispatcher(..).forward(..)
for 15 years. A legendary framework from legendary developers (Joe Walnes, Mike Cannon-Brookes). Here’s the critical class. You will want to copy sections of this codebase (particularly the tricks they are doing to ‘request’ and ‘response’) if you are going in the direction I outline.
I’ve made an example of an ‘a’ and ‘b’ web-app scenario. It is on Github: github.com/paul-hammant/servletDispatcherTestand there are instructions there as to how to deploy it yourself for a tryout. There are servlets/filters and static resources. Shown is accessing those things directly, for via in implicit getContext('/b').getRequestDispatcher(..).include(..)
route. I’ve cheated and used mime types of “text/plain” to make the code smaller, but it would be the same for HTML, XML, GIFs, JSON, etc. It also goes out of its way to show that one-thread is used for all of a request.
Thanks to
Joe Walnes (of Sitemesh fame) and Mark Thomas (Tomcat mail-list)