Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Интеграция Spring + Velocity + Tiles
Постоянная ссылка 05-07-2007 anydoby java

Несколько недель назад столкнулся с проблемой интеграции Tiles и Velocity в рамках Spring MVC. Оказалось, что готового решения в Spring нет. И в интернете информации об этом немного, одни вопросы, поэтому решил поисследовать вопрос. И вот, что из этого вышло. Tiles можно использовать вместе с Velocity при помощи Velocity Tools, но вместе с Spring это не работает.

Вот какое решение я предлагаю.

Предполагается, что все ресурсы находятся в папке WEB-INF.

Создаем Velocity шаблон, в котором будут находиться фрагменты страниц (WEB-INF/layouts/template.vm):


<html>
  <head>
  </head>
  <body>
     #parse($tiles.getAttribute("header"))
     #parse($tiles.getAttribute("central"))
     #parse($tiles.getAttribute("bottom"))
  </body>
</html>

header, central и bottom это названия атрибутов фрагментов, которые будут вставляться в шаблон.

Файл определений tiles (tiles-defs.xml) выглядит вот так, примерно:


<!DOCTYPE tiles-definitions PUBLIC
  "-//Apache Software Foundation//DTD Tiles Configuration 1.3//EN"
  "http://struts.apache.org/dtds/tiles-config_1_3.dtd">
<tiles-definitions>
	<definition name="main_base" page="/layouts/template.vm">
		<put name="header" value="/layouts/header.vm" />
	</definition>

	<definition name="page.tile" extends="main_base">
		<put name="central" value="messages.vm" />
	</definition>

</tiles-definitions>

Далее следует конфигурация для Spring:

Бин конфигурации tiles


<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer">
	<property name="factoryClass" value="org.apache.struts.tiles.xmlDefinition.I18nFactorySet"/>
	<property name="definitions">
		<list>
			<value>/WEB-INF/config/tiles-defs.xml</value>
		</list>
	</property>
</bean>

View resolver и движок velocity


<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
	<property name="resourceLoaderPath" value="/WEB-INF/views" />
	<property name="velocityProperties">
		<props>
			<prop key="input.encoding">utf-8</prop>
			<prop key="output.encoding">utf-8</prop>
		</props>
	</property>
</bean>

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
	<property name="cache" value="true" />
	<property name="prefix" value="" />
	<property name="exposeSpringMacroHelpers" value="true" />
	<property name="order" value="1"></property>
	<property name="contentType" value="text/html; charset=utf-8"></property>
	<property name="toolboxConfigLocation" value="WEB-INF/config/velocity-toolbox.xml"></property>
  	<property name="suffix" value=".tile" />
  	<property name="viewClass" value="com.yoursite.VelocityTilesView" />
</bean>

Не забудьте создать конфигурационный файл для Velocity tools:


<?xml version="1.0"?>
<toolbox>
  <tool>
     <key>tiles</key>
     <scope>request</scope>
     <class>org.apache.velocity.tools.struts.TilesTool</class>
  </tool>
</toolbox>

И наконец, создаем класс, который будет обрабатывать views, он и делает основную работу, вставляя фрагменты в шаблоны Tiles:


package com.yoursite;

import java.util.Map;
import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.struts.tiles.*;
import org.apache.velocity.context.Context;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.view.velocity.VelocityToolboxView;
import org.springframework.web.util.WebUtils;

public class VelocityTilesView extends VelocityToolboxView {

    /**
     * Name of the attribute that will override the path of the layout page to render. A Tiles
     * component controller can set such an attribute to dynamically switch the look and feel of a
     * Tiles page.
     * 
     * @see #setPath
     */
    public static final String PATH_ATTRIBUTE = VelocityTilesView.class.getName() + ".PATH";

    private DefinitionsFactory definitionsFactory;

    /**
     * Constructor for use as a bean.
     */
    public VelocityTilesView() {}

    /**
     * Create a new InternalResourceView with the given URL.
     */
    public VelocityTilesView(String url) {
        setUrl(url);
    }

    /**
     * Render the internal resource given the specified model. This includes setting the model as
     * request attributes.
     */
    protected void renderMergedTemplateModel(Map model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        exposeHelpers(model, request);
        Context velocityContext = createVelocityContext(model, request, response);
        exposeHelpers(velocityContext, request, response);
        exposeToolAttributes(velocityContext, request);
        exposeModelAsRequestAttributes(model, request);

        String tile = prepareForRendering(request, response);

        mergeTemplate(getTemplate(tile), velocityContext, response);
    }

    /**
     * Determine whether to use RequestDispatcher's <code>include</code> or <code>forward</code>
     * method.
     * <p>
     * Performs a check whether an include URI attribute is found in the request, indicating an
     * include request, and whether the response has already been committed. In both cases, an
     * include will be performed, as a forward is not possible anymore.
     */
    protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
        return (WebUtils.isIncludeRequest(request) || response.isCommitted());
    }

    /**
     * Expose the current request URI and paths as {@link HttpServletRequest} attributes under the
     * keys defined in the Servlet 2.4 specification, for Servlet 2.3-containers.
     * <p>
     * Does not override values if already present, to not conflict with Servlet 2.4+ containers.
     */
    protected void exposeForwardRequestAttributes(HttpServletRequest request) {
        WebUtils.exposeForwardRequestAttributes(request);
    }

    /**
     * Overrides checkTemplate in VelocityView to avoid throwing an exception if it can't find a
     * file.
     */
    protected void checkTemplate() throws ApplicationContextException {
    // skip check.
    }

    /**
     * Set the path of the layout page to render.
     */
    public static void setPath(HttpServletRequest request, String path) {
        request.setAttribute(PATH_ATTRIBUTE, path);
    }

    protected void initApplicationContext() throws ApplicationContextException {
        super.initApplicationContext();

        // get definitions factory
        this.definitionsFactory = (DefinitionsFactory) getServletContext().getAttribute(
                TilesUtilImpl.DEFINITIONS_FACTORY);
        if (this.definitionsFactory == null) {
            throw new ApplicationContextException(
                    "Tiles definitions factory not found: TilesConfigurer not defined?");
        }
    }

    /**
     * Prepare for rendering the Tiles definition: Execute the associated component controller if
     * any, and determine the request dispatcher path.
     */
    protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        // get component definition
        ComponentDefinition definition = getComponentDefinition(this.definitionsFactory, request);
        if (definition == null) {
            throw new ServletException("No Tiles definition found for name '" + getUrl() + "'");
        }

        // get current component context
        ComponentContext context = getComponentContext(definition, request);

        // execute component controller associated with definition, if any
        Controller controller = getController(definition, request);
        if (controller != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Executing Tiles controller [" + controller + "]");
            }
            executeController(controller, context, request, response);
        }

        // determine the path of the definition
        String path = getDispatcherPath(definition, request);
        if (path == null) {
            throw new ServletException("Could not determine a path for Tiles definition '"
                    + definition.getName() + "'");
        }

        return path;
    }

    /**
     * Determine the Tiles component definition for the given Tiles definitions factory.
     */
    protected ComponentDefinition getComponentDefinition(DefinitionsFactory factory,
            HttpServletRequest request) throws Exception {
        return factory.getDefinition(getUrl(), request, getServletContext());
    }

    /**
     * Determine the Tiles component context for the given Tiles definition.
     */
    protected ComponentContext getComponentContext(ComponentDefinition definition,
            HttpServletRequest request) throws Exception {
        ComponentContext context = ComponentContext.getContext(request);
        if (context == null) {
            context = new ComponentContext(definition.getAttributes());
            ComponentContext.setContext(context, request);
        } else {
            context.addMissing(definition.getAttributes());
        }
        return context;
    }

    /**
     * Determine and initialize the Tiles component controller for the given Tiles definition, if
     * any.
     */
    protected Controller getController(ComponentDefinition definition, HttpServletRequest request)
            throws Exception {
        return definition.getOrCreateController();
    }

    /**
     * Execute the given Tiles controller.
     */
    protected void executeController(Controller controller, ComponentContext context,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        controller.execute(context, request, response, getServletContext());
    }

    /**
     * Determine the dispatcher path for the given Tiles definition, i.e. the request dispatcher
     * path of the layout page.
     */
    protected String getDispatcherPath(ComponentDefinition definition, HttpServletRequest request)
            throws Exception {
        Object pathAttr = request.getAttribute(PATH_ATTRIBUTE);
        return (pathAttr != null ? pathAttr.toString() : definition.getPath());
    }
}

Вот, собственно, и все. Теперь можно создавать ModelAndView , у которого view установлен в page, который определен в tiles-defs.xml.

PS: недавно вышла новая версия Tiles, в которой авторы отвязались наконец от Struts, я написал плагин, который интегрирует Spring MVC, Velocity и теперь уже Tiles 2 - прошу сюда.

Добавить комментарий

sharoeb
27-03-2010

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

Предыдущая статья JScrollPane не обновляет скролбары при изменении размеров компонентов наследников Следующая статья Свой сервер