цели
Когда я использую плитки, мне не нравится подход по умолчанию (?) К 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
|
@Bean public VelocityConfig velocityConfig() { VelocityConfigurer cfg = new VelocityConfigurer(); cfg.setResourceLoaderPath( '/WEB-INF/velocity/' ); cfg.setConfigLocation(context .getResource( '/WEB-INF/velocity.properties' )); return cfg; } @Bean public 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
|
@Bean public 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 и контекстно-зависимые инструменты. - Метод
TilesRequestContext
inwrite
прежнему содержит исходный контекст Velocity, созданный изTilesRequestContext
Spring. Стандартная реализацияVelocityAttributeRenderer
просто выбрасывает его. Этот обходной путь выше извлекает исходный контекст и использует его для рендеринга.
Вывод
Это путешествие заняло гораздо больше времени, чем я думал. Документации по таким случаям не существует, поэтому я часами отлаживал, читал исходный код, экспериментировал, молился и ругался. Это было еще более увлекательно, так как у меня были почти нулевые знания о внутреннем разрешении и движке рендеринга Spring, а также Tiles и Velocity.
Это очень приятно, так как я много узнал обо всех этих технологиях и в конце концов смог решить их довольно элегантно. Но это была также неприятная и отнимающая много времени загадка, и я надеюсь, что этот пост избавит кого-то от неприятностей.
Обновление — Инструменты Скорости
Некоторое время спустя я обнаружил, что это решение не поддерживает свойство Velocity Tools. Вот как это сделать: Инструменты Spring & Velocity .
Ссылка: Интеграция Spring, Velocity и Tiles от нашего партнера JCG Конрада Гаруса в блоге Белки .