Velocity Фильтр

April 7, 2008

Готовясь написать серию заметок про Velocity, решил я пока выложить один из своих ранних проектов. Сервлет-фильтр, выполняющий прозрачную генерацию html-документов с помощью Velocity. Если вы не знаете что такое Velocity, то это универсальный движок шаблонификации (для java). Velocity может быть использован для генерации документа на основании шаблона и "что-там-нужно-вставить-в-шаблоне-в-нужные-места". Velocity умеет интегрироваться в spring и работать как его View-слой. Вот только для небольших проектов тянуть на хостинг spring или что-там-еще-умеет-работать-с-velocity избыточно (ага еще, spring нужно изучить).

В составе Velocity есть собственное решение интеграции с веб-приложениями. Идея в том, что в составе velocity есть специальный сервлет VelocityServlet. А вы должны создать свой сервлет как наследник от этого VelocityServlet и перекрыть в нем метод handleRequest. В качестве входного параметра вам передается Контекст, который нужно заполнить значениями (в самом конце эти значения будут вставлены внутрь шаблона velocity).
  1. protected Template handleRequest(Context ctx) throws Exception { 
  2.         throw new Exception("You must override VelocityServlet.handleRequest( Context)  
  3.           or VelocityServlet.handleRequest( HttpServletRequest,  HttpServletResponse, Context)"); 
  4.     }
Да, чуть не забыл вам еще нужно как-то и где-то найти этот самый Template и вернуть его из метода (не сложно, но рутинно).

Все классно и стильно, но при разработке небольших проектов, создание списка servelter-ов неудобно. На каждое действие нужен свой сервлет, в случае изменения кода сервлета, нужно перезапускать веб-приложение, и на все это тратится время (я же сказал, что это маленький проект). А вот jsp-страницы как раз более гибки: пиши код, ничего не нужно регистрировать в web.xml, за обновлениями кода страницы следит сам контейнер сервлетов (да, да, jsp-страницы так знакомы и привычны тем, кто пишет на php и пытается одним глазком взглянуть на java).

Вот только, практика написания jsp страниц прямо таки побуждает смешивать html и java-код (логику работы с данными). А раз побуждает, то jsp - это зло и его нужно избегать. Стоп, а если все перевернуть с ног на голову?

jsp содержит только логику, только java код и никакого html. Результаты расчетов должны быть помещены внутрь атрибутов запроса или сессии или чего-то там еще. Затем эти данные будут помещены внутрь шаблона Velocity. Если фраза "логика внутри jsp" вас напрягает, то замените слово "jsp" на "сервлеты" (мой фильтр умеет работать и в таком режиме).

Краткий пример howto:

1. Создаю веб-приложение следующего вида:



Обратите внимание на то, что у меня есть два файла a3.jsp (логика и код) и a3.vm (шаблон Velocity)

Теперь нужно создать конфигурационный файл web.xml, указать в нем настройки для VeloFilter (так называется мой класс сервлета-фильтра):
  1. <!-- filters -->
  2.     <filter>
  3.         <filter-name>VeloProcessor</filter-name>
  4.         <filter-class>black.ti.server.VeloFilter</filter-class>
  5.         <init-param>
  6.             <param-name>inNameTemplate</param-name>
  7.             <param-value>^/([^/]*)/(.*)\.velo(.*)$</param-value>
  8.         </init-param>
  9.         <init-param>
  10.             <param-name>outNameTemplate</param-name>
  11.             <param-value>/$2.jsp$3</param-value>
  12.         </init-param>
  13.         <init-param>
  14.             <param-name>veloNameTemplate</param-name>
  15.             <param-value>/$2.vm</param-value>
  16.         </init-param>
  17.         <init-param>
  18.             <param-name>veloConfigLocation</param-name>
  19.             <param-value>WEB-INF/veloconf.properties</param-value>
  20.         </init-param>
  21.         <init-param>
  22.             <param-name>passToVeloCompoundObjects</param-name>
  23.             <param-value>true</param-value>
  24.         </init-param>
  25.     </filter>
  26.     <filter-mapping>
  27.         <filter-name>VeloProcessor</filter-name>
  28.         <url-pattern>/*</url-pattern>
  29.     </filter-mapping>
Основное внимание на параметр inNameTemplate - он задает регулярное выражение которое будет применяться ко всем входящим запросам, так в примере, если запрошенная страница заканчивается на velo (расширение velo), то фильтр должен быть применен.

Второй параметр outNameTemplate задает правило перезаписи адреса, т.е. какое имя будет того файла который должен быть вызван и выполнить реальную работу. В примере выполняется перезапись адреса вида page.velo в страницу page.jsp.

Третий параметр veloNameTemplate управляет тем как будут называться файлы шаблонов velocity. В примере имя шаблона получается путем замены расширения запрошенной страницы на vm (адрес page.velo будет обрабатываться шаблоном page.vm).

Четвертый параметр veloConfigLocation указывает на то где находится конфигурационный файл для Velocity.

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

Теперь я приведу пример конфигурационного файла veloconf.properties
resource.loader = file
file.resource.loader.description = Velocity File Resource Loader
file.resource.loader.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
file.resource.loader.path = resources/templates/
file.resource.loader.cache = false
file.resource.loader.modificationCheckInterval = 0
Обратите внимание, я указываю откуда будут загружаться шаблоны (методика настройки Velocity стандартная, никаких дублирующихся настроек для моего сервлета фильтра не будет).

Теперь перейдем к примеру:

Я создаю файл a3.jsp следующего вида:
  1. <%@ page import="java.util.ArrayList" %>
  2. <%@ page import="java.util.Date" %>
  3. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  4. <%
  5.     request.setCharacterEncoding("utf-8");
  6.  
  7.     ArrayList al = new ArrayList();
  8.     for (int i = 0; i < 100; i++){
  9.         al.add("array."+i);
  10.     }
  11.     request.setAttribute("array", al);
  12.     session.setAttribute("max", 234123);
  13. %>
  14.  
  15. Это полня чушь
  16. Нет ничего существенного <%= new Date() %>
Как видите, здесь смесь и логики и визуализации. Внутрь объекта request я добавил новое свойство с данными (массив) "array".

Теперь пример файла шаблона:
  1. <h1>$fio</h1>
  2. <h2>$param.fio</h2>
  3.  
  4. <h3>$cookie</h3>
  5.  
  6. <h3>$session</h3>
  7.  
  8.     #foreach ($a_el in $array)
  9.         <h1>
  10.             $a_el
  11.         </h1>
  12.     #end
Здесь я ссылаюсь на какие-то переменные: fio, param.fio, cookie, session. Ага, вот знакомое имя переменной - array (я использовал его в предыдущем файле - jsp).

Запрашиваю страницу с именем a3.velo (файла с таким расширением у нас физически нет).
 http://center:8080/velo/a3.velo?fio=Вася Тапкин
Как только пришел запрос на получение файла a3.velo мой фильтр вычислил имя того файла который нужно запросить a3.jsp. Запустил его, полностью проигнорировав весь html-код, который я намешал в нем. Затем фильтр передает управление внутрь шаблона a3.vm, найденного в папке resources/templates/. Предварительно в контекст помещаются все переменные которые были переданы через параметры запроса (в адресной строке есть переменная fio). Также были помещены переменные из сессии и cookie и request. Обращаться ко всем этим переменным можно как напрямую по их имени так и с указанием предшествующего контекста (request, session, cookie, param). Второй способ предпочтительнее, если вы боитесь что какие-то из переменных могут иметь одинаковые имена.

А вот и результат работы скрипта:
Вася Тапкин
Вася Тапкин
{}
{max=234123}
array.0
array.1
array.2
array.3 
.....
 <a href="https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/java/velofilter">https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/java/velofilter</a>