Данный пост написан не с целью развития холивара на тему, что лучше Velocity или JSP, но здесь я хочу продемонстрировать как можно подключать такой удобный шаблонный движок, как Velocity к приложению, построенному на базе Guice.
О том, что такое Velocity и многих аспектах его использования можно почитать на форуме программистов и конечно же официальной странице проекта.
Во-первых сделаем наше приложение впринципе независимым от используемого шаблонного движка. Т.е. обеспечим возможность подключать не только Velocity но и любой шаблонизатор. Для этого создадим простой интерфейс шаблонного движка:
package ru.sposition.web.view;
import java.io.Writer;
import java.util.Map;
import ru.sposition.web.view.exception.TemplateEngineException;
public interface ITemplateEngine {
public void mergeTemplate(String templateLocation, Map<String, Object> model,
Writer writer) throws TemplateEngineException;
public void mergeTemplate(String templateLocation, String encoding,
Map<String, Object> model, Writer writer) throws TemplateEngineException;
public String mergeTemplateIntoString(String templateLocation,
Map<String, Object> model) throws TemplateEngineException;
public String mergeTemplateIntoString(String templateLocation, String encoding,
Map<String, Object> model) throws TemplateEngineException;
}
import java.io.Writer;
import java.util.Map;
import ru.sposition.web.view.exception.TemplateEngineException;
public interface ITemplateEngine {
public void mergeTemplate(String templateLocation, Map<String, Object> model,
Writer writer) throws TemplateEngineException;
public void mergeTemplate(String templateLocation, String encoding,
Map<String, Object> model, Writer writer) throws TemplateEngineException;
public String mergeTemplateIntoString(String templateLocation,
Map<String, Object> model) throws TemplateEngineException;
public String mergeTemplateIntoString(String templateLocation, String encoding,
Map<String, Object> model) throws TemplateEngineException;
}
Все взаимодействие приложения с механизмом шаблонов теперь достаточно строить через данный интерфейс, он предоставляет все необходимые для этого методы. Для того, чтобы использовать в качестве шаблонного движка Velocity необходимо реализовать данный интерфейс с использованием VelocityEngine. Собственно данный механизм реализован в проекте Spring Framework (класс VelocityEngineUtils), код данной реализации и возьмем за основу, слегка его модифицировав:
package ru.sposition.web.view.velocity;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import ru.sposition.web.view.ITemplateEngine;
import ru.sposition.web.view.exception.TemplateEngineException;
import ru.sposition.web.view.velocity.annotations.Velocity;
import com.google.inject.Inject;
/**
* Utility class for working with a VelocityEngine. Provides convenience methods
* to merge a Velocity template with a model.
*
* @author Juergen Hoeller
* @author Samolisov Pavel <samolisov@gmail.com>
*/
public class VelocityTemplateEngine implements ITemplateEngine {
private VelocityEngine velocityEngine;
@Inject
public void injectVelocitiEngine(@Velocity VelocityEngine engine) {
this.velocityEngine = engine;
}
/**
* Merge the specified Velocity template with the given model and write the
* result to the given Writer.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param model the Map that contains model names as keys and model objects as values
* @param writer the Writer to write the result to
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public void mergeTemplate(String templateLocation, Map<String, Object> model,
Writer writer) throws TemplateEngineException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, velocityContext, writer);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new TemplateEngineException(ex.getMessage());
}
}
/**
* Merge the specified Velocity template with the given model and write the
* result to the given Writer.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects as values
* @param writer the Writer to write the result to
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public void mergeTemplate(String templateLocation, String encoding,
Map<String, Object> model, Writer writer) throws TemplateEngineException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new TemplateEngineException(ex.getMessage());
}
}
/**
* Merge the specified Velocity template with the given model into a String.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param model the Map that contains model names as keys and model objects as values
* @return the result as String
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public String mergeTemplateIntoString(String templateLocation,
Map<String, Object> model) throws TemplateEngineException {
StringWriter result = new StringWriter();
mergeTemplate(templateLocation, model, result);
return result.toString();
}
/**
* Merge the specified Velocity template with the given model into a String.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects as values
* @return the result as String
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public String mergeTemplateIntoString(String templateLocation, String encoding,
Map<String, Object> model) throws TemplateEngineException {
StringWriter result = new StringWriter();
mergeTemplate(templateLocation, encoding, model, result);
return result.toString();
}
}
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import ru.sposition.web.view.ITemplateEngine;
import ru.sposition.web.view.exception.TemplateEngineException;
import ru.sposition.web.view.velocity.annotations.Velocity;
import com.google.inject.Inject;
/**
* Utility class for working with a VelocityEngine. Provides convenience methods
* to merge a Velocity template with a model.
*
* @author Juergen Hoeller
* @author Samolisov Pavel <samolisov@gmail.com>
*/
public class VelocityTemplateEngine implements ITemplateEngine {
private VelocityEngine velocityEngine;
@Inject
public void injectVelocitiEngine(@Velocity VelocityEngine engine) {
this.velocityEngine = engine;
}
/**
* Merge the specified Velocity template with the given model and write the
* result to the given Writer.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param model the Map that contains model names as keys and model objects as values
* @param writer the Writer to write the result to
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public void mergeTemplate(String templateLocation, Map<String, Object> model,
Writer writer) throws TemplateEngineException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, velocityContext, writer);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new TemplateEngineException(ex.getMessage());
}
}
/**
* Merge the specified Velocity template with the given model and write the
* result to the given Writer.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects as values
* @param writer the Writer to write the result to
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public void mergeTemplate(String templateLocation, String encoding,
Map<String, Object> model, Writer writer) throws TemplateEngineException {
try {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new TemplateEngineException(ex.getMessage());
}
}
/**
* Merge the specified Velocity template with the given model into a String.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param model the Map that contains model names as keys and model objects as values
* @return the result as String
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public String mergeTemplateIntoString(String templateLocation,
Map<String, Object> model) throws TemplateEngineException {
StringWriter result = new StringWriter();
mergeTemplate(templateLocation, model, result);
return result.toString();
}
/**
* Merge the specified Velocity template with the given model into a String.
*
* @param templateLocation the location of template, relative to Velocity's resource loader path
* @param encoding the encoding of the template file
* @param model the Map that contains model names as keys and model objects as values
* @return the result as String
* @throws TemplateEngineException if the template wasn't found or rendering failed
*/
public String mergeTemplateIntoString(String templateLocation, String encoding,
Map<String, Object> model) throws TemplateEngineException {
StringWriter result = new StringWriter();
mergeTemplate(templateLocation, encoding, model, result);
return result.toString();
}
}
Вот теперь и начинается самое интересное - нам нужно создать экземпляр класса VelocityEngine и инъекцировать его в VelocityTemplateEngine. Само по себе создание объекта данного класса - задача не сложная, сложности возникают тогда, когда этот объект надо сконфигурировать. Дело в том, что для корректной работы Velocity ему необходимо указать как минимум - путь к каталогу с шаблонами, кодировку этих шаблонов и кодировку результата. Есть и ряд дополнительных параметров, но я не стал с ними заморачиватся, кому интересно сможет разобраться, посмотрев на конфигурацию кодировок. Замечу, что в проекте Spring Framework существует фабрика VelocityEngineFactory, которую мы и возьмем за основу нашего Guice-конфигуратора.
Сразу разберемся с терминологией. В терминологии Guice фабрика объектов называется провайдером. Про механизм расширения Guice-провайдеров я уже писал ранее. Сейчас мы попробуем написать провайдер, который создает объект класса VelocityEngine, используя конфигурации заданные с помощью инъекции констант (в Spring Framework используется xml). Повторюсь, что в данной реализации настраивается только путь к каталогу с шаблонами, кодировка входных файлов, кодировка результата, остальное впринципе не сложно добавить. Начнем с кода провайдера:
package ru.sposition.web.view.velocity.provider;
import java.io.IOException;
import java.util.HashMap;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import ru.sposition.web.view.velocity.provider.annotations.InputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.OutputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.ResourceLoaderPath;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class VelocityEngineProvider implements Provider<VelocityEngine> {
@Inject
@ResourceLoaderPath
private String resourceLoaderPath;
private HashMap<String, String> properties = new HashMap<String, String>();
@Inject
public void injectInputEncoding(@InputEncoding String inputEncoding) {
properties.put("input.encoding", inputEncoding);
}
@Inject
public void injectOutputEncoding(@OutputEncoding String outputEncoding) {
properties.put("output.encoding", outputEncoding);
}
public VelocityEngine get() {
try {
VelocityEngine velocityEngine = newVelocityEngine();
if (resourceLoaderPath != null)
initVelocityResourceLoader(velocityEngine, resourceLoaderPath);
for (String property : properties.keySet()) {
velocityEngine.setProperty(property, properties.get(property));
}
velocityEngine.init();
return velocityEngine;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
return new VelocityEngine();
}
protected void initVelocityResourceLoader(VelocityEngine engine, String resourceLoaderPath) {
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resourceLoaderPath);
}
public static Provider<VelocityEngine> initProvider() {
return new VelocityEngineProvider();
}
}
import java.io.IOException;
import java.util.HashMap;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import ru.sposition.web.view.velocity.provider.annotations.InputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.OutputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.ResourceLoaderPath;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class VelocityEngineProvider implements Provider<VelocityEngine> {
@Inject
@ResourceLoaderPath
private String resourceLoaderPath;
private HashMap<String, String> properties = new HashMap<String, String>();
@Inject
public void injectInputEncoding(@InputEncoding String inputEncoding) {
properties.put("input.encoding", inputEncoding);
}
@Inject
public void injectOutputEncoding(@OutputEncoding String outputEncoding) {
properties.put("output.encoding", outputEncoding);
}
public VelocityEngine get() {
try {
VelocityEngine velocityEngine = newVelocityEngine();
if (resourceLoaderPath != null)
initVelocityResourceLoader(velocityEngine, resourceLoaderPath);
for (String property : properties.keySet()) {
velocityEngine.setProperty(property, properties.get(property));
}
velocityEngine.init();
return velocityEngine;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
return new VelocityEngine();
}
protected void initVelocityResourceLoader(VelocityEngine engine, String resourceLoaderPath) {
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resourceLoaderPath);
}
public static Provider<VelocityEngine> initProvider() {
return new VelocityEngineProvider();
}
}
Основа кода провайдера - метод get(). В данном методе создается новый экземпляр класса VelocityEnginе и заполняется параметрами, которые инъектируются в класс. Инъекция параметров осуществляется с помощью соответствующих методов и фактически просиходит добавление ЗНАЧЕНИЙ параметров в специальный Map. Данный Map хранит соответствие имен параметров в терминологии VelocityEngine и их значений. Соответствие имен и значений задается аннотациями: @ResourceLoaderPath, @InputEncoding, @OutputEncoding. Данные аннотации - ничто иное как стандартные биндинг-аннотации. Все они имеют весьма тривиальный код, который покажу на примере @InputEncoding:
package ru.sposition.web.view.velocity.provider.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface InputEncoding {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface InputEncoding {
}
Про настройку инъектирования с помощью аннотаций говорилось здесь.
Небольшое замечание по поводу параметра resourceLoaderPath. В ДАННОЙ реализации он должен быть равен АБСОЛЮТНОМУ пути к каталогу с шаблонами. В фабрике из Spring Framework такого ограничения нет, но там и более сложный процесс разрешения этого пути с использованием специальных лоадеров. Тянуть их из Spring Framework я посчитал нецелесообразным.
Ну и теперь пришло время собрать все эти механизмы в отдельный модуль - ViewModule (view - вид, в терминах MVC). Код модуля будет следующий:
package ru.sposition.web.view;
import org.apache.velocity.app.VelocityEngine;
import ru.sposition.web.view.annotations.TemplateEngine;
import ru.sposition.web.view.velocity.VelocityTemplateEngine;
import ru.sposition.web.view.velocity.annotations.Velocity;
import ru.sposition.web.view.velocity.provider.VelocityEngineProvider;
import ru.sposition.web.view.velocity.provider.annotations.InputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.OutputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.ResourceLoaderPath;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
public class ViewModule extends AbstractModule {
private static final String RESOURCE_LOADER_PATH = "/WEB-INF/velocity/";
private static final String INPUT_ENCODING = "utf-8";
private static final String OUTPUT_ENCODING = "utf-8";
private final String resourcePathPrefix;
public ViewModule(String resourcePathPrefix) {
this.resourcePathPrefix = resourcePathPrefix;
}
@Override
protected void configure() {
bindConstant()
.annotatedWith(ResourceLoaderPath.class)
.to(resourcePathPrefix + RESOURCE_LOADER_PATH);
bindConstant()
.annotatedWith(InputEncoding.class)
.to(INPUT_ENCODING);
bindConstant()
.annotatedWith(OutputEncoding.class)
.to(OUTPUT_ENCODING);
bind(VelocityEngine.class)
.annotatedWith(Velocity.class)
.toProvider(VelocityEngineProvider.initProvider())
.in(Scopes.SINGLETON);;
bind(ITemplateEngine.class)
.annotatedWith(TemplateEngine.class)
.to(VelocityTemplateEngine.class)
.in(Scopes.SINGLETON);
}
}
import org.apache.velocity.app.VelocityEngine;
import ru.sposition.web.view.annotations.TemplateEngine;
import ru.sposition.web.view.velocity.VelocityTemplateEngine;
import ru.sposition.web.view.velocity.annotations.Velocity;
import ru.sposition.web.view.velocity.provider.VelocityEngineProvider;
import ru.sposition.web.view.velocity.provider.annotations.InputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.OutputEncoding;
import ru.sposition.web.view.velocity.provider.annotations.ResourceLoaderPath;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
public class ViewModule extends AbstractModule {
private static final String RESOURCE_LOADER_PATH = "/WEB-INF/velocity/";
private static final String INPUT_ENCODING = "utf-8";
private static final String OUTPUT_ENCODING = "utf-8";
private final String resourcePathPrefix;
public ViewModule(String resourcePathPrefix) {
this.resourcePathPrefix = resourcePathPrefix;
}
@Override
protected void configure() {
bindConstant()
.annotatedWith(ResourceLoaderPath.class)
.to(resourcePathPrefix + RESOURCE_LOADER_PATH);
bindConstant()
.annotatedWith(InputEncoding.class)
.to(INPUT_ENCODING);
bindConstant()
.annotatedWith(OutputEncoding.class)
.to(OUTPUT_ENCODING);
bind(VelocityEngine.class)
.annotatedWith(Velocity.class)
.toProvider(VelocityEngineProvider.initProvider())
.in(Scopes.SINGLETON);;
bind(ITemplateEngine.class)
.annotatedWith(TemplateEngine.class)
.to(VelocityTemplateEngine.class)
.in(Scopes.SINGLETON);
}
}
Как видим в конструкторе данный модуль получает параметр resourcePathPrefix - это префикс к каталогу /WEB-INF/velocity/ в файловой системе, фактически это каталог, в котором находится наше веб-приложение. Получать этот префикс мы будем с помощью сервлет-апи. В случае если пишется не веб-приложение, а, например, десктоп-приложение, получать каталог в котором оно установлено придется системными средствами.
Ну и продемонстрируем использование созданного механизма. Создадим несложный сервлет, который парсит Velocity-шаблон и выводит результат в окно браузера. Код сервлета следующий:
package ru.sposition.web.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ru.sposition.web.view.ITemplateEngine;
import ru.sposition.web.view.annotations.TemplateEngine;
import ru.sposition.web.view.exception.TemplateEngineException;
import com.google.inject.Inject;
import com.google.inject.servlet.InjectedHttpServlet;
@SuppressWarnings("serial")
public class VelocityDemoServlet extends InjectedHttpServlet {
private ITemplateEngine templateEngine;
@Inject
public void injectTemplateEngine(@TemplateEngine ITemplateEngine engine) {
templateEngine = engine;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Map<String, Object> model = new HashMap<String, Object>();
model.put("hello", "Welcome to Hell!");
model.put("title", "Demo servlet title");
response.setContentType("text/html; charset=UTF-8");
try {
templateEngine.mergeTemplate("index.vm", model, response.getWriter());
} catch (TemplateEngineException e) {
e.printStackTrace();
throw new ServletException(e.getMessage());
}
}
}
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ru.sposition.web.view.ITemplateEngine;
import ru.sposition.web.view.annotations.TemplateEngine;
import ru.sposition.web.view.exception.TemplateEngineException;
import com.google.inject.Inject;
import com.google.inject.servlet.InjectedHttpServlet;
@SuppressWarnings("serial")
public class VelocityDemoServlet extends InjectedHttpServlet {
private ITemplateEngine templateEngine;
@Inject
public void injectTemplateEngine(@TemplateEngine ITemplateEngine engine) {
templateEngine = engine;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Map<String, Object> model = new HashMap<String, Object>();
model.put("hello", "Welcome to Hell!");
model.put("title", "Demo servlet title");
response.setContentType("text/html; charset=UTF-8");
try {
templateEngine.mergeTemplate("index.vm", model, response.getWriter());
} catch (TemplateEngineException e) {
e.printStackTrace();
throw new ServletException(e.getMessage());
}
}
}
Как видим сервлет даже не знает ничего о Velocity, он инъектирует шаблонный движок (ITemplateEngine) и его уже использует. Напомню, что для разрешения зависимостей сервлету нужен Injector, который он пытается взять из своего контекста. Создается Injector и помещается в контекст-сервлета с помощью ServletContextListener'а:
package ru.sposition.web.controller;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import ru.sposition.web.view.ViewModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.servlet.ServletModule;
public class VelocityDemoServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute(Injector.class.getName(), getInjector(servletContext));
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.removeAttribute(Injector.class.getName());
}
protected Injector getInjector(ServletContext servletContext) {
return Guice.createInjector(new Module[] {new ServletModule(),
new ViewModule(servletContext.getRealPath("/"))});
}
}
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import ru.sposition.web.view.ViewModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.servlet.ServletModule;
public class VelocityDemoServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute(Injector.class.getName(), getInjector(servletContext));
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.removeAttribute(Injector.class.getName());
}
protected Injector getInjector(ServletContext servletContext) {
return Guice.createInjector(new Module[] {new ServletModule(),
new ViewModule(servletContext.getRealPath("/"))});
}
}
Код servletContext.getRealPath("/") как раз таки и служит для получения каталога, в который установлено веб-приложение.
Ну и напоследок - код демонстрационного шаблона:
<?xml version="1.0" encoding="windows-1251"?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">
<head>
<title>${title}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="/style.css" />
</head>
<body>
<h1>${hello}</h1>
</body>
</html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">
<head>
<title>${title}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="/style.css" />
</head>
<body>
<h1>${hello}</h1>
</body>
</html>
Вот собственно и все :) - веб приложение, использующее Velocity создано. Как всегда, готов ответить на ваши вопросы, с радостью приму любые коментарии, замечания, дополнения.
Понравилось сообщение - подпишись на блог