Что же это за шаблонизатор и с чем его готовить?
Так вот ничего революционного этот проект собой не представляет, но как оказалось он весьма удобен. Концепция проста - зачем работать с различного рода "обвесами" на странице (headers, footers, sidebars), если в действительности в контексте логики приложения нас интересует только конкретный динамический блок (при этом, совершенно необязательно, что "обвесы" являются статическими блоками). Таким образом, основная выгода - это разделение логики представления по нескольким уровням (да, это тривиально).
Что дает такое простое разбиение:
- больше нет необходимости постоянно передавать в представление множество повторяющихся значений
- больше нет необходимости работать со всем шаблоном, теперь достаточно вернуть из контроллера идентификатор конкретного динамического блока
По полочкам...
Каким же образом использовать этот инструмент? Все достаточно просто, необходимо сделать следующее:
- Сконфигурировать Tiles в контексте Spring контейнера. Spring уже включает поддержку Tiles, поэтому для этого достаточно лишь создать Configurer и соответствующий ViewResolver:
<!-- Configure Apache Tiles for the view -->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles/tiles-templates.xml" />
<property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.tiles2.TilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean> - Создать tiles-конфигурацию ("/WEB-INF/tiles/tiles-templates.xml" из spring-конфига), в которой описываются шаблоный, отдельные элементы шаблона.
- Реализовать представление для описанных шаблонов
Начинаем разработку
Прежде чем перейти к конфигурации Spring и Tiles в качестве примера определим структуру шаблона. В качестве примера возьмем следующую структуру:
Исходя из такой структуры можно заметить, что фактическая работа приложения сводиться к работе блоком CONTENT, все остальное либо не связанно с контентом, либо является статическими блоками (конечно, бывают исключения ^_^).
Конфигурируем Spring
В конфигурации Spring MVC ничего особенного нет, кроме выше изложенного ничего специфического не добавляем.
/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>И /WEB-INF/spring/mvc-config.xml:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.page</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.page</welcome-file>
</welcome-file-list>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>Более тут добавить нечего, кроме того, что необходимо реализовать контроллер для нашего примера. Нужно заметить, что ничего принципиально не меняется и здесь, кроме того, что нас больше не интересуют данные для любых других блоков шаблона, кроме контентного:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- Scan for annotation based controllers -->
<context:component-scan base-package="ru.sultry.controllers">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- Configure Apache Tiles for the view -->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles/tiles-templates.xml" />
<property name="preparerFactoryClass" value="org.springframework.web.servlet.view.tiles2.SpringBeanPreparerFactory"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.tiles2.TilesViewResolver">
<property name="requestContextAttribute" value="requestContext"/>
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>
</beans>
package ru.sultry.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* @author tsarev.oi@mail.ru
* User: Oleg Tsarev
* Date: 10.11.2010
* Time: 14:17:42
*
*/
@Controller
public class MainController {
public static final String INDEX_PAGE = "/index.page";
public static final String INDEX_VIEW = "main";
public static final String INFO_PAGE = "/info.page";
public static final String INFO_VIEW = "info";
@RequestMapping(value=INDEX_PAGE)
public ModelAndView index(Model model) {
model.addAttribute("message", "Message from main controller to main page!");
return new ModelAndView(INDEX_VIEW);
}
@RequestMapping(value=INFO_PAGE)
public ModelAndView info(Model model) {
model.addAttribute("message", "Message from main controller to info page!");
return new ModelAndView(INFO_VIEW);
}
}
Конфигурируем Tiles
Конфигурация Tiles описывает шаблоны и элементы этих шаблонов. Конечно, конфигурацию можно разнести по нескольким файлам, например завести отдельные конфигурации для шаблонов и отдельных компонентов шаблонов или для шаблонов и всех наследников этого шаблона. В качестве применения шаблона используется привычное любому программисту наследование при котором можно переопределять отдельные элементы. В принципе, возможно полностью переопределить шаблон на n-ом уровне наследования, но как правило это не имеет никакого смысла.
Все элементы конфигурации - definitions являются дочерними к корневому элементу tiles-definitions:
<?xml version="1.0" encoding="UTF-8"?>Приступим к объявлению нового шаблона под выбранную выше структуру. На данном этапе предположим, что все блоки шаблона кроме контентного будут статическими:
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
</tiles-definitions>
<!-- Templates -->В данном объявлении шаблон под именем base-template связывается с представлением, расположенным в "/WEB-INF/templates/base-template.jsp". Кроме этого, в качестве атрибутов предаются ссылки на статические элементы - header, navigation, content, footer, а также строковый атрибут со значением заголовка по умолчанию. К реализации представления и применению в шаблоне внедренных атрибутов мы перейдем позже, а сейчас необходимо отметить следующее - при попытке обращения к шаблону из контроллера (при обращении к представлению base-template) получим (должны получить, так как представление еще не реализовано) страницу с заголовком по умолчанию, заполненными блоками header, footer, navigation, но с отсутствующим контентным блоком.
<definition name="base-template" template="/WEB-INF/templates/base-template.jsp">
<put-attribute name="title" value="Default title" />
<put-attribute name="header" value="/WEB-INF/templates/header.jsp" />
<put-attribute name="navigation" value="/WEB-INF/templates/navigation.jsp" />
<put-attribute name="content" value="" />
<put-attribute name="footer" value="/WEB-INF/templates/footer.jsp" />
</definition>
Приступим к наследованию этого шаблона для конкретных страниц, обрабатываемых нашим контроллером:
<definition name="main" extends="base-template">
<put-attribute name="title" value="View: main" />
<put-attribute name="content" value="/WEB-INF/templates/layouts/main.jsp" />
</definition>
<definition name="info" extends="base-template">
<put-attribute name="title" value="View: info" />
<put-attribute name="content" value="/WEB-INF/templates/layouts/info.jsp" />
</definition>
Объявлять представление для каждой новой страницы? Необязательно, в определениях tiles можно использовать регулярные выражения (причем допустимы два синтаксиса), поэтому предыдущее объявление можно заменить на следующее:
<definition name="*" extends="base-template">У подобного объявления есть недостатки, при обращении к несуществующей странице в браузер будет выкинуто сообщение об исключении при поиске соответствующего файла для контентного блока. Но эта ситуация обходится несколькими способами, кроме того никто не мешает переопределить поведение Tiles в этом случае, так как доступны исходники. ^_^
<put-attribute name="title" value="View: {1}" />
<put-attribute name="content" value="/WEB-INF/templates/layouts/{1}.jsp" />
</definition>
Реализуем представление
В представлении нет ничего особенного, кроме наличия специфичных для Tiles включения и использования атрибутов, но их использование достаточно тривиально, так как достаточно знать только имена внедренных атрибутов из конфигурации Tiles.
/WEB-INF/templates/base-template.jsp:
<%@page contentType="text/html" pageEncoding="UTF-8"%>/WEB-INF/templates/header.jsp:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>
<tiles:getAsString name="title" />
</title>
<link rel="stylesheet" href="/css/style.css" type="text/css"/>
</head>
<body>
<tiles:insertAttribute name="header" />
<tiles:insertAttribute name="navigation" />
<tiles:insertAttribute name="content" />
<tiles:insertAttribute name="footer" />
</body>
</html>
<%@page contentType="text/html" pageEncoding="UTF-8"%>/WEB-INF/templates/navigation.jsp:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="header">Header</div>
<%@page contentType="text/html" pageEncoding="UTF-8"%>/WEB-INF/templates/footer.jsp:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="navigation">
<ul>
<li>
<a href="/index.page" title="Main page">Main page</a>
</li>
<li>
<a href="/info.page" title="Information page">Information page</a>
</li>
</ul>
</div>
<%@page contentType="text/html" pageEncoding="UTF-8"%>Представления для самого шаблона реализованы, в base-template.jsp происходит вызов вложенных представлений по ссылкам, которые уже были переданы через атрибуты.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="footer">Footer</div>
Теперь необходимо реализовать представление конкретных страниц, обрабатываемых контроллером.
/WEB-INF/templates/layouts/main.jsp и /WEB-INF/templates/layouts/info.jsp:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="content">
<c:if test="${message != null}">
<c:out value="${message}"/>
</c:if>
</div>
Все представления описаны, поэтому уже можно увидеть результаты работы. Для такого простого примера все достаточно просто и в целом не вызывает вопросов, но, что если некоторые блоки шаблона также являются динамическими. Передача данных для такого блока через контроллер фактически вернет нас к объединению логики приложения и представления.
Независимые динамические блоки
Было бы удобно, чтобы некоторые блоки шаблона можно было бы связать с некотором контроллером именно для этого блока, который будет вызываться независимо от других контроллеров. В Tiles до второй версии это реализовывалось расширением специального класса контроллера (который предоставлял Spring), но так как теперь разница между контроллерами в Spring нивелировалось, то нам достаточно использовать обычный контроллер, который необходимо будет привязать к конкретному блоку в конфигурации Tiles. Просто так взять контроллер и связать его с шаблоном право тоже не выйдет, но Tiles предоставляет интересную стратегию для предобработчиков, которые уже связываются с конкретным блоком.
Предобработчики должны расширять класс ViewPreparerSupport, предоставляемый Tiles. Так как других требований нет, то этот предобработчик может диспетчезироваться из Spring как обычный контроллер, что позволяет использовать автоприсвоение (autowiring) и т.д. и т.п.
В качестве примера динамического блока возьмем статический на данный момент блок заголовка. НО начнем с написания контроллера:
package ru.sultry.controllers;Как видно из примера, в данном контроллере имеется доступ к данным передаваемым из основного контроллера, а также к атрибутам Tiles. При этом данный предобработчик находиться под управлением Spring, что позволяет связать его со всем приложением.
import org.apache.tiles.AttributeContext;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.preparer.ViewPreparerSupport;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
/**
* @author tsarev.oi@mail.ru
* User: Oleg Tsarev
* Date: 10.11.2010
* Time: 15:06:25
*
*/
@Controller("headerController")
@Scope("session")
public class HeaderController extends ViewPreparerSupport{
@Override
public void execute(TilesRequestContext tilesContext,
AttributeContext attributeContext) {
// Get access to model parameters from MainController as example
String message = (String) tilesContext.getRequestScope().get("message");
tilesContext.getRequestScope().put("headerMessage", "Message from header!");
}
}
Теперь необходимо изменить представление блока заголовка и связать definition с конкретным предобработчиком. Представление измениться следующим образом:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div id="header">
Header<br/>
<c:if test="${headerMessage != null}">
<c:out value="${headerMessage}"/>
</c:if>
</div>
А для того, чтобы связать заголовок с предобработчиком придется изменить и описание шаблона:
<!-- Templates -->Вот и все, теперь контроллер будет передавать собственное сообщение в представление.
<definition name="base-template" template="/WEB-INF/templates/base-template.jsp">
<put-attribute name="title" value="Default title" />
<put-attribute name="header" value="header" />
<put-attribute name="navigation" value="/WEB-INF/templates/navigation.jsp" />
<put-attribute name="content" value="" />
<put-attribute name="footer" value="/WEB-INF/templates/footer.jsp" />
</definition>
<definition name="header"
preparer="headerController"
template="/WEB-INF/templates/header.jsp">
</definition>
Важно обратить внимание, что предобработчики отрабатывают после основного конроллера, поэтому при неосторожности можно переопределить те данные которые вернул главный контроллер.
С таким функционалом и базовым наследованием, которые предоставляет Tiles можно организовывать очень гибкую шаблонизацию. А при некотором старании размер конфигурации Tiles может быть минимальным, что избавляет разработчика от бесконечных "портянок" в конфигурационных файлах.
Отличная статейка! Спасибо, будем пользовать :)
ОтветитьУдалитьне работает
ОтветитьУдалитьSEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
Данное исключение связанно с тем, что в вашем classpath нет данного класса. В данном случае необходимо добавить библиотеку slf4j.
Удалитьв pom.xml нужно добавить:
ОтветитьУдалитьorg.slf4j
slf4j-simple
1.6.1
org.apache.tiles
tiles-api
2.2.2
org.apache.tiles
tiles-core
2.2.2
org.apache.tiles
tiles-jsp
2.2.2
org.apache.tiles
tiles-servlet
2.2.2
org.apache.tiles
tiles-template
2.2.2
Все верно, я опустил полное описание зависимостей и оставил это на усмотрение разработчиков. Все же существует множество инструментов сборок и диспетчеризации зависимостей.
Удалить"Конфигурируем Tiles" - в каких файлах это писать?
ОтветитьУдалить/WEB-INF/tiles/tiles-templates.xml - это именно конфигурация шаблонов Tiles.
УдалитьСпасибо, получилось. Теперь пытаюсь прикрутить поверх tiles, webflow уже 2 недели ничего не выходит.
ОтветитьУдалитьделаю по этой статье http://www.springbyexample.org/examples/simple-spring-web-flow-webapp.html не получается смапить не могу понять где задаётся вебфлов'у какие url он должен обрабатывать...
ОтветитьУдалитьHi,
ОтветитьУдалитьThanks for this tutorial, can you please post the source code ?
Thanks