Данное руководство детально описывает процесс создания портлета для Liferay портала cредствами фреймворка Wicket.
Все части руководства:
- Портлетный проект для Liferay портала
- Конфигурация Wicket-портлета, или web.xml, portlet.xml и т.д., и т.п.
- Разработка функциональности Wicket-портлета
- Если используется Spring Framework
Исходный код разрабатываемого примера доступен на GitHub.
“Весеннее” достижение
Если ты, уважаемый читатель, хорошо знаком с программистским фольклором и до сих пор наивно полагаешь, что Java – это “язык индустриальной эпохи”, и в его экосистеме не осталось такого места, которое не было бы загрязнено выбросами “фабрик”, то боюсь, конечно, тебя разочаровывать, но, по всей видимости, ты безнадежно отстал от жизни. Ибо с середины 2000х годов Java – это все-таки скорее и чаще Spring Framework, “Dependency Injection” и все, что с этим связано.
Одной из частей рассмативаемого нами в этом руководстве фреймворка Wicket является модуль, который занимается его интеграцией со Spring. Таким образом, если требуется применить IoC, вынести какую-либо конфигурацию в XML, настроить авторизацию с помощью Spring Security, воспользоваться AOP или, страшно сказать, задействовать в проекте мессаджинг с помощью JMS – Wicket не станет помехой для реализации всех этих вещей с помощью Spring Framework. На самом деле, Wicket просто выступит в качестве представления для всей остальной логики приложения, большая часть которой может быть написана как раз с использованием Spring.
Согласись, было бы неплохо, если бы всеми инструментами Spring мы могли бы воспользоваться и при разработке портлета на Wicket?
Собственно, в этой части руководства мы и рассмотрим, как можно разработать портлет, используя совместно Wicket и Spring Framework. Точнее, мы модифицируем Wicket-портлет, разработанный в предыдущих частях, добавив в него поддержку Spring. Назовем новую версию портлета “Весенним” достижением” – “Spring Achievement”.
Чтобы наш новый пример не показался уж слишком оторванным от жизни, давай хотя бы воспользуемся в портлете одной из основных частей Spring, а именно паттерном “Dependency Injection”. Я предлагаю прислушаться к никогда не стареющему правилу “Не набирать, а выбирать” и сделать так, чтобы человек, совершивший достижение, мог выбрать себе награду из заранее подготовленного списка наград. Список наград в нашем примере будет формироваться сервисом, который в свою очередь будет инъектиться на страницу с помощью Spring.
Причем здесь Spring MVC?
Сразу расставлю все точки над “и” (тем более, что в русском языке над “и” нет никаких точек вообще) и скажу, что портлет, который мы собираемся создать, будет всего-навсего лишь модифицированным Spring MVC портлетом. То есть сам по себе это будет Spring MVC портлет, реализуемый классом org.springframework.web.portlet.DispatcherPortlet, но в качестве представлений для разных режимов портлета будут использоваться написанные нами ранее классы Wicket. Для краткости в дальнейшем, заменив “View” в MVC на “Wicket”, будем называть наш портлет Spring MWC портлетом. Пусть это и не совсем точная и вообще придуманная мной пять минут назад формулировка, зато как свежо и изящно!
Одной из самых интересных особенностей портлетного Spring MVC приложения является, на мой взгляд, наличие отдельного локального WebApplicationContext у каждого из портлетов. Эта особенность будет перенята и нашим Spring MWC портлетом.
Таким образом, каждый экземпляр Spring MWC портлета будет иметь собственный WebApplicationContext, который унаследует также и все bean’ы, описанные в корневом контексте приложения. Помимо того, что любой bean из корневого контекста может быть переопределен в контексте портлета (нам впрочем этого делать не потребуется), в этом контексте можно также определить множество других bean’ов с локальной для портлета областью видимости.
Но вначале разберемся с корневым WebApplicationContext приложения…
Web.xml и корневой WebApplicationContext
Конфигурирование корневого контекста Spring, bean’ы которого имеют глобальную для всех портлетов область видимости, происходит достаточно стандартным образом:
1 2 3 4 5 6 7 8 9 10 |
|
В файле /WEB-INF/applicationContext.xml, который, как несложно заметить, и используется для инициализации корневого WebApplicationContext, мы опишем всего два bean’а. Одним из них будет bean сервиса для получения списка наград – чтобы можно было пользоваться одним и тем же объектом сервиса во всех экземплярах портлета – но о нем мы поговорим позже, в соответствующем разделе. Сейчас же рассмотрим второй bean из корневого контекста – bean портлетного приложения:
1 2 3 |
|
Ссылку на bean приложения нужно указать в описании фильтра в web.xml. Для этого вместо параметра applicationClassName используем два других параметра: один – applicationFactoryClassName со значением “org.apache.wicket.spring.SpringWebApplicationFactory”, и второй – applicationBean, как раз и содержащий ссылку на bean приложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Portlet.xml и контекст портлета
В файле portlet.xml вместо класса org.apache.wicket.protocol.http.portlet.WicketPortlet, как я и обещал, мы используем портлетный класс Spring MVC – org.springframework.web.portlet.DispatcherPortlet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
Единственный новый параметр инициализации здесь – параметр contextConfigLocation, в котором нужно указать путь до конфигурационного файла, используемого для инициализации локального WebApplicationContext портлета. Остальные параметры остаются такими же, как и в предыдущей не-Spring’овой версии портлета, так как суровая правда состоит в том, что DispatcherPortlet будет лишь “оберткой” для лежащего под ним WicketPortlet. “Оберткой”, впрочем, в крайней степени умелой, способной поддерживать не только корневой WebApplicationContext, но и локальный контекст приложения портлета.
Если ты, дорогой читатель, внимательно следил за развитием событий до текущего момента, то у тебя могло возникнуть два вполне закономерных вопроса:
- Если при инициализации портлета в качестве параметра инициализации мы указываем лишь contextConfigLocation, то каким образом мы сконфигурируем DispatcherPortlet, чтобы он в итоге передавал управление WicketPortlet?
- И зачем нам вообще может понадобиться WebApplicationContext портлета, если мы уже решили реализовать сервис наград в корневом контексте приложения?
Забавно, но ответ на оба этих вопроса в действительности одинаков: WebApplicationContext портлета, в числе всего прочего, и есть то место, где происходит настройка DispatcherPortlet.
Перечень всех bean’ов, которые используются для настройки DispatcherPortlet, можно найти в официальной документации по фреймворку Spring MVC. Мы используем всего два bean’а. Первый – с типом org.springframework.web.portlet.handler.PortletModeHandlerMapping – используется для установки контроллеров для обработки различных режимов портлета. В нашем случае этот bean для всех трех режимов – VIEW, EDIT и HELP – ссылается на один и тот же контроллер, в качестве которого выступает второй bean – achievementPortletController:
1 2 3 4 5 6 7 8 9 10 11 |
|
Bean achievementPortletController имеет тип org.springframework.web.portlet.mvc.PortletWrappingController и как раз передает управление WicketPortlet:
1 2 3 4 5 6 7 |
|
Сервис для получения списка наград
Вдоволь насмотревшись на перепетии настройки конфигурационных файлов портлета со Spring в одной из главных ролей, перейдем, наконец, к самой приятной части разработки и реализуем сервис для получения списка наград.
Вначале интерфейс:
1 2 3 4 |
|
Далее, предположив, что мы реализовали каким-нибудь образом этот интерфейс в классе PrizeServiceImpl, передадим Spring управление жизненным циклом реализации. Объявляем bean сервиса в корневом контексте портлета:
1 2 3 |
|
Далее инъектим сервис на страницу редактирования с помощью привычной аннотации @SpringBean:
1 2 3 4 5 6 7 |
|
При этом не забываем, что, чтобы аннотация @SpringBean работала, нам нужно установить SpringComponentInjector в качестве слушателя инициализации Wicket-компонентов в классе приложения:
1 2 3 4 5 6 7 8 9 |
|
В конце концов, используем сервис в коде комбобокса для выбора награды:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Результат
Думаю, нельзя не согласиться, что со Spring наш портлет стал еще чуточку удобнее:
Осталось сказать только, что исходники портлета, модифицированного в сторону использования в нем Spring Framework, можно увидеть в общем с предыдущей версией портлета репозитории на Github.