цели
Когда я использую плитки, мне не нравится подход по умолчанию (?) К tiles.xml . Я не хочу помещать импорт JS & CSS, заголовка страницы, навигации, тела и т. Д. Каждый в отдельный файл, как показано во фрагменте ниже, потому что это заставляет меня переключаться между окнами редактора.
|
1
2
3
4
5
6
|
<definition name='hello' template='/WEB-INF/templates/main.jsp'> <put-attribute name='title' value='Hello' type='string' /> <put-attribute name='head' value='/WEB-INF/templates/hello-js-and-css.jsp' /> <put-attribute name='nav' value='/WEB-INF/templates/hello-nav.jsp' /> <put-attribute name='body' value='/WEB-INF/templates/hello.jsp' /></definition> |
Очевидно, я тоже не хочу помещать слишком много деталей в tiles.xml .
Что мне действительно нравится, так это наличие одного файла на страницу, сборка шаблона в одном месте, например, этот фрагмент JSP:
|
01
02
03
04
05
06
07
08
09
10
|
<tiles:insertTemplate template='template.jsp'> <tiles:putAttribute name='title' value='Hello' /> <tiles:putAttribute name='head'> <script type='text/javascript' src='/js/jQuery.js' /> <script type='text/javascript' src='/js/hello.js' /> </tiles:putAttribute> <tiles:putAttribute name='body'> <div>Hello, world!</div> </tiles:putAttribute></tiles:insertTemplate> |
В Velocity это должно выглядеть так :
|
01
02
03
04
05
06
07
08
09
10
|
#tiles_insertTemplate({'template': 'template.vm'}) #tiles_putAttribute({'name':'title', 'value': 'Hello'})#end #tiles_putAttribute({'name':'head'}) <script type='text/javascript' src='/js/jQuery.js' /> <script type='text/javascript' src='/js/hello.js' /> #end #tiles_putAttribute({'name':'body'}) <div>Hello, world!</div> #end#end |
Тем не менее, документы по интеграции действительно предназначены для добавления некоторой поддержки Velocity в ваше приложение на основе Tiles, хотя я хотел совсем наоборот: использовать Tiles в моем многофункциональном приложении Velocity с полной поддержкой весеннего контекста, макросов и т. Д.
Решение
Короче говоря, то, что мы собираемся сделать, это:
- Используйте
VelocityViewResolverдля разрешения и рендеринга страниц. - Добавить поддержку макросов Tiles в этот движок рендеринга Velocity
- Расширьте рендерер Tiles с полной поддержкой Velocity, включая контекст Spring, макросы и т. Д. В конечном итоге мы собираемся использовать его для оригинального движка Velocity, созданного Spring.
Полный исходный код в виде минимального, полного веб-приложения находится на github . Подробности смотрите ниже.
Весна и Скорость -> Плитка
Для первого шага мы определяем viewResolver и viewResolver следующим образом:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@Beanpublic VelocityConfig velocityConfig() { VelocityConfigurer cfg = new VelocityConfigurer(); cfg.setResourceLoaderPath('/WEB-INF/velocity/'); cfg.setConfigLocation(context .getResource('/WEB-INF/velocity.properties')); return cfg;}@Beanpublic ViewResolver viewResolver() { VelocityViewResolver resolver = new VelocityViewResolver(); resolver.setViewClass(VelocityToolboxView.class); resolver.setSuffix('.vm'); return resolver;} |
Важно, чтобы мы использовали VelocityToolboxView , иначе директивы плиток не будут работать.
Нам также нужно поместить в velocity.properties следующее:
|
01
02
03
04
05
06
07
08
09
10
|
userdirective=org.apache.tiles.velocity.template.AddAttributeDirective,\ org.apache.tiles.velocity.template.AddListAttributeDirective,\ org.apache.tiles.velocity.template.DefinitionDirective,\ org.apache.tiles.velocity.template.GetAsStringDirective,\ org.apache.tiles.velocity.template.ImportAttributeDirective,\ org.apache.tiles.velocity.template.InsertAttributeDirective,\ org.apache.tiles.velocity.template.InsertDefinitionDirective,\ org.apache.tiles.velocity.template.InsertTemplateDirective,\ org.apache.tiles.velocity.template.PutAttributeDirective,\ org.apache.tiles.velocity.template.PutListAttributeDirective |
Это добавляет базовую поддержку директив Tiles в Velocity, но все равно бесполезно, потому что, как только Velocity передает рендеринг в Tiles, Tiles не может рендерить Velocity и просто игнорирует его (отображая синтаксис #directives для браузера.
Плитка -> Скорость
Нам нужно научить Tiles использовать Velocity. Для этого нам понадобится пользовательский TilesInitializer :
|
1
2
3
4
5
6
|
@Beanpublic TilesConfigurer tilesConfigurer() { TilesConfigurer cfg = new TilesConfigurer(); cfg.setTilesInitializer(new VelocityTilesInitializer(velocityConfig())); return cfg;} |
|
01
02
03
04
05
06
07
08
09
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
35
36
37
38
39
40
41
|
public class VelocityTilesInitializer extends DefaultTilesInitializer { private VelocityConfig velocityConfig; public VelocityTilesInitializer(VelocityConfig velocityConfig) { this.velocityConfig = velocityConfig; } @Override protected AbstractTilesContainerFactory createContainerFactory( TilesApplicationContext context) { return new BasicTilesContainerFactory() { @Override protected List<TilesRequestContextFactory> getTilesRequestContextFactoriesToBeChained( ChainedTilesRequestContextFactory parent) { List<TilesRequestContextFactory> factories = super .getTilesRequestContextFactoriesToBeChained(parent); registerRequestContextFactory( VelocityTilesRequestContextFactory.class.getName(), factories, parent); return factories; } @Override protected AttributeRenderer createTemplateAttributeRenderer( BasicRendererFactory rendererFactory, TilesApplicationContext applicationContext, TilesRequestContextFactory contextFactory, TilesContainer container, AttributeEvaluatorFactory attributeEvaluatorFactory) { ContextPassingVelocityAttributeRenderer var = new ContextPassingVelocityAttributeRenderer( velocityConfig.getVelocityEngine()); var.setApplicationContext(applicationContext); var.setRequestContextFactory(contextFactory); var.setAttributeEvaluatorFactory(attributeEvaluatorFactory); var.commit(); return var; } }; }} |
Мы почти у цели, но тут есть хитрость. Обычно в строках 31-32 вы бы поместили velocityAttributeRenderer . Однако этот рендерер полностью игнорирует контекст и механизм Spring-augmented Velocity, которые Tiles получил от Velocity. Он создает собственный VelocityEngine и позволяет ему выполнять рендеринг, отбрасывая все директивы Spring и tile и контекстные объекты.
Невозможно изменить это поведение в Tiles (что в противном случае представляется интересным исследованием шаблонов проектирования и расширяемости). Я даже создал две проблемы JIRA для этого: 541 для пересылки контекста и 542 для внедрения VelocityEngine .
Между тем, мы должны обойтись этим обходным путем ( полный ресурс см. В github ):
|
01
02
03
04
05
06
07
08
09
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
35
36
37
38
|
public class ContextPassingVelocityAttributeRenderer extends AbstractTypeDetectingAttributeRenderer { // ... private VelocityEngine engine; public ContextPassingVelocityAttributeRenderer(VelocityEngine engine) { this.engine = engine; } // ... public void commit() { velocityView = new VelocityView(new TilesApplicationContextJeeConfig()); velocityView.setVelocityEngine(engine); } @Override public void write(Object value, Attribute attribute, TilesRequestContext request) throws IOException { if (value != null) { if (value instanceof String) { InternalContextAdapter adapter = (InternalContextAdapter) ((VelocityTilesRequestContext) request) .getRequestObjects()[0]; Context context = adapter.getInternalUserContext(); Template template = velocityView.getTemplate((String) value); velocityView.merge(template, context, request.getWriter()); } else { throw new InvalidTemplateException( 'Cannot render a template that is not a string: ' + value.toString()); } } else { throw new InvalidTemplateException('Cannot render a null template'); } } // ... |
Он решает обе проблемы JIRA и позволяет нам достичь конечной цели:
-
VelocityEngineвнедренный вVelocityViewявляется оригинальнымVelocityEngineиз Spring. Помимо прочего, он поддерживает директивы Spring и контекстно-зависимые инструменты. - Метод
TilesRequestContextinwriteпрежнему содержит исходный контекст Velocity, созданный изTilesRequestContextSpring. Стандартная реализацияVelocityAttributeRendererпросто выбрасывает его. Этот обходной путь выше извлекает исходный контекст и использует его для рендеринга.
Вывод
Это путешествие заняло гораздо больше времени, чем я думал. Документации по таким случаям не существует, поэтому я часами отлаживал, читал исходный код, экспериментировал, молился и ругался. Это было еще более увлекательно, так как у меня были почти нулевые знания о внутреннем разрешении и движке рендеринга Spring, а также Tiles и Velocity.
Это очень приятно, так как я много узнал обо всех этих технологиях и в конце концов смог решить их довольно элегантно. Но это была также неприятная и отнимающая много времени загадка, и я надеюсь, что этот пост избавит кого-то от неприятностей.
Обновление — Инструменты Скорости
Некоторое время спустя я обнаружил, что это решение не поддерживает свойство Velocity Tools. Вот как это сделать: Инструменты Spring & Velocity .
Ссылка: Интеграция Spring, Velocity и Tiles от нашего партнера JCG Конрада Гаруса в блоге Белки .