教程:扩展Thymeleaf

Thymeleaf

教程:扩展Thymeleaf - 图1

教程:扩展Thymeleaf

文件版本:20181029-2018年10月29日

项目版本:3.0.11

项目网站:https://www.thymeleaf.org

1扩展Thymeleaf的一些理由

Thymeleaf是一个非常可扩展的库。它的关键在于,它的大多数面向用户的特性并不是直接内置到它的核心中,而是被打包和组件化成称为方言.

图书馆提供两种方言。标准SpringStandard方言,但你可以很容易地创造自己的。让我们探讨一下这样做的一些原因:

场景1:向标准方言添加功能

假设您的应用程序使用SpringStandard方言和它需要显示一个蓝色或红色背景警告文本框取决于用户的角色(管理员或非管理员)从星期一到星期六,但总是绿色星期日。您可以使用模板上的条件表达式来计算这一点,但是太多的条件会使您的代码有点难以阅读…。

解决方案:创建一个名为alertclass和一个属性处理器(将计算正确的CSS类的Java代码),并将其打包到您自己的MyOwnDialect方言。将此方言添加到模板引擎中。th前缀(与SpringStandard(第一)你现在可以使用th:alertclass="${user.role}"!

场景2:视图层组件

假设您的公司广泛使用Thymeleaf,您希望创建一个通用功能库(标记和/或属性),您可以在多个应用程序中使用这些功能,而不必将它们从一个应用程序复制到另一个应用程序。这就是,您希望以类似于jsp的方式创建视图层组件。塔格利布.

解决方案:为每一组相关功能创建一个Thymeleaf方言,并根据需要将这些方言添加到您的应用程序中。请注意,如果这些方言中的标记或属性使用外部(国际化)消息,您将能够将这些消息与您的方言一起打包(形状为处理器消息)而不是要求您的所有应用程序都将它们包含在它们的消息中。.properties文件与JSP文件一样。

场景3:创建自己的模板系统

现在想象一下,您正在创建一个公共网站,允许用户创建自己的设计模板来显示他们的内容。当然,您不希望您的用户能够在他们的模板中完成任何事情,甚至不希望标准方言允许的所有内容(例如,执行OGNL表达式)。因此,您需要向用户提供这样的能力:在他们的模板中只添加一组在您控制下的非常具体的功能(比如显示个人资料照片、博客条目文本等)。

解决方案:创建一个具有您希望用户能够使用的标记或属性的Thymeleaf方言,如<mysite:profilePhoto /><mysite:blogentries fromDate="23/4/2011" />…然后允许用户使用这些特性创建自己的模板,让Thymeleaf执行它们,确保没有人会做他们不允许做的事情。

2个方言和处理器

2.1.方言

如果你读过UsingThymeleaf到这里之前的教程(你应该这么做),你应该知道你一直在学的东西并不是完全正确的Thymeleaf,而是它的标准方言(或者Spring 标准方言,如果您也读过Thymeleaf+Spring教程)。

那是什么意思?这意味着所有这些th:x您学会使用的属性只是一组标准的、开箱即用的特性集,但是您可以用您希望使用的名称定义您自己的属性集(或标记),并在Thymeleaf中使用它们来处理您的模板。你可以定义你自己的方言。

方言是实现org.thymeleaf.dialect.IDialect接口,它不能再简单了:

  1. public interface IDialect {
  2. public String getName();
  3. }

方言的唯一核心要求是要有一个可以用来识别它的名字。但是这本身并没有多大用处,所以方言通常会实现一个或几个子接口IDialect,取决于它们为Thymeleaf引擎提供了什么:

  • IProcessorDialect的方言处理器.
  • IPreProcessorDialect的方言预处理器.
  • IPostProcessorDialect的方言后处理器.
  • IExpressionObjectDialect的方言表达式对象.
  • IExecutionAttributeDialect的方言执行属性.

处理器方言:IProcessorDialect

这个IProcessorDialect接口如下所示:

  1. public interface IProcessorDialect extends IDialect {
  2. public String getPrefix();
  3. public int getDialectProcessorPrecedence();
  4. public Set<IProcessor> getProcessors(final String dialectPrefix);
  5. }

处理器是负责执行Thymeleaf模板中大多数逻辑的对象,也可能是最重要的Thymeleaf扩展工件。我们将在下一节中更详细地介绍处理器。

这种方言只定义了三个项目:

  • 这个prefix,它是应该应用的前缀或命名空间。默认情况下与方言处理器匹配的元素和属性。所以一个带前缀的方言th例如标准方言将能够定义匹配属性的处理器,例如th:text, th:ifth:whatever(或data-th-text, data-th-ifdata-th-whatever如果我们愿意纯HTML 5(语法)但是请注意,方言在这里返回的前缀是只有默认的用于该方言,但这种前缀可以在模板引擎配置期间更改。还要注意前缀可以是null如果我们希望我们的处理器在没有前缀的标记/属性上执行。
  • 这个dialog precedence允许对不同方言的处理器进行排序。处理器定义自己的优先值,但考虑到这些处理器的先例。相对于方言优先,因此,只需设置正确的方言优先值,就可以将特定方言中的每个处理器配置为在来自不同方言的所有处理器之前执行。
  • 这个处理器如其名称所示,是处理器由方言提供。注意getProcessors(...)方法传递dialectPrefix作为参数,如果方言已在模板引擎中配置,其前缀与默认前缀不同。很可能是IProcessor实例在初始化过程中将需要此信息。

预处理方言:IPreProcessorDialect

预处理器后处理器处理器在这个过程中,它们不是在单个事件或事件模型(模板的一个片段)上执行,而是作为引擎处理链中的一个附加步骤应用于整个模板执行过程。因此,它们遵循的api与处理器完全不同,更多的是面向事件的,由较低级别定义的。ITemplateHandler接口。

在预处理器的具体情况下,它们适用于以前Thymeleaf引擎开始执行特定模板的处理器。

这个IPreProcessorDialect接口看起来如下:

  1. public interface IPreProcessorDialect extends IDialect {
  2. public int getDialectPreProcessorPrecedence();
  3. public Set<IPreProcessor> getPreProcessors();
  4. }

非常类似于IProcessorDialect上面—包括它自己的方言级优先级—用于预处理—但是缺少一个前缀,因为预处理器根本不需要它(他们不需要)匹配在特定的事件上—相反,它们会处理所有的事件)。

后处理器方言:IPostProcessorDialect

如上所述,后处理器是模板执行链中的另一个步骤,但这一次它们执行。Thymeleaf引擎已经应用了所有需要的处理器。这意味着后处理器在模板输出发生之前应用(因此可以修改输出内容)。

这个IPostProcessorDialect接口看起来如下:

  1. public interface IPostProcessorDialect extends IDialect {
  2. public int getDialectPostProcessorPrecedence();
  3. public Set<IPostProcessor> getPostProcessors();
  4. }

…完全类似于IPreProcessorDialect接口,但在这种情况下,当然是针对后处理程序的。

表达对象方言:IExpressionObjectDialect

实现此接口的方言提供了新的表达式对象表达式实用对象可以在模板中任何位置的表达式中使用,例如#strings, #numbers, #dates等由标准方言提供。

这个IExpressionObjectDialect接口如下所示:

  1. public interface IExpressionObjectDialect extends IDialect {
  2. public IExpressionObjectFactory getExpressionObjectFactory();
  3. }

,正如我们所看到的,它不返回表达式对象本身,而是只返回工厂…原因是表达式对象可能需要来自处理上下文的数据才能生成,因此在真正处理模板…之前不可能构建这些数据。另外,大多数表情都不需要表达式对象所以建造它们更好按需,只有当特定表达式真正需要它们时(并且只构建那些需要的表达式)。

这是IExpressionObjectFactory接口:

  1. public interface IExpressionObjectFactory {
  2. public Map<String,ExpressionObjectDefinition> getObjectDefinitions();
  3. public Object buildObject(final IProcessingContext processingContext, final String expressionObjectName);
  4. }

执行属性方言:IExecutionAttributeDialect

实现此接口的方言允许提供执行属性,即在模板处理过程中被执行的每个处理器都可以使用的对象。

例如,标准方言实现了这个接口,以便向每个处理器提供:

  • 这个Thymeleaf标准表达式解析器以便可以解析和执行任何属性中的标准表达式。
  • 这个可变表达式计算器所以${...}表达式在OGNL或Springel中执行(取决于我们是否使用Spring集成模块)。
  • 这个转换处中执行转换操作的${{...}}表情。

请注意,这些对象在上下文中不可用,因此不能从模板表达式中使用它们。它们的可用性仅限于扩展点的实现,如处理器、预处理器等.

这个IExecutionAttributeDialect接口非常简单:

  1. public interface IExecutionAttributeDialect extends IDialect {
  2. public Map<String,Object> getExecutionAttributes();
  3. }

2.2.处理器

处理器是实现org.thymeleaf.processor.IProcessor接口,它们包含要应用于模板不同部分(我们将表示为事件,因为Thymeleaf是一个基于事件的引擎)。这个接口如下所示:

  1. public interface IProcessor {
  2. public TemplateMode getTemplateMode();
  3. public int getPrecedence();
  4. }

与方言一样,这是一个非常简单的接口,它只指定可以应用处理器的模板模式及其优先级。

但是有几种类型的处理机,针对每一种可能类型的事件:

  • 模板开始/结束
  • 元素标签
  • 文本
  • 评论意见
  • CDATA部分
  • DOCTYPE条款
  • XML声明
  • 处理指令

也是为了模型事件序列表示整体元素,即一个具有其整个主体的元素,包括任何嵌套元素或可能出现在其内部的任何其他类型的工件。如果建模的元素是独立元素,模型将只包含其相应的事件;但是,如果建模的元素有一个主体,则模型将包含其中的每个事件。开放标签向其封闭标签*都包括在内。

所有这些类型的处理器都是通过实现特定的接口或扩展可用的一个处理器来创建的。抽象实现…所有这些符合Thymeleaf3.0处理器API的工件都位于org.thymeleaf.processor包裹。

元素处理器

元素处理器是在开式元件 (IOpenElementTag)或独立元素 (IStandaloneElementTag)事件,通常是通过将元素的名称(和/或其属性之一)与处理器指定的匹配配置相匹配。这就是IElementProcessor接口看起来如下:

  1. public interface IElementProcessor extends IProcessor {
  2. public MatchingElementName getMatchingElementName();
  3. public MatchingAttributeName getMatchingAttributeName();
  4. }

但是请注意,元素处理器实现并不意味着直接实现这个接口。相反,元素处理器应该分为两类中的一种:

  • 元素标记处理器,实现IElementTagProcessor接口。这些处理器在只打开/独立标记事件(不能将任何处理器应用于封闭标签),并且没有对元素主体的直接访问。
  • 元素模型处理器,实现IElementModelProcessor接口。这些处理器在完全元素,包括他们的身体IModel物品。

我们应该分别查看这些接口:

元素标记处理器:IElementTagProcessor

元素标记处理器,如所解释的,在单个开式元件独立元素标记,与其匹配的配置匹配(见IElementProcessor)。要实现的接口是IElementTagProcessor,看起来是这样的:

  1. public interface IElementTagProcessor extends IElementProcessor {
  2. public void process(
  3. final ITemplateContext context,
  4. final IProcessableElementTag tag,
  5. final IElementTagStructureHandler structureHandler);
  6. }

正如我们所看到的,除了扩展IElementProcessor它只指定process(...)方法时将执行的匹配配置匹配(并按其所确定的顺序排列)优先,在IProcessor(超级界面)。这个process(...)签名非常紧凑,并且遵循每个Thymeleaf处理器接口中的模式:

  • 这个process(...)方法返回void…任何操作都将通过structureHandler.
  • 这个context参数包含执行模板的上下文:变量、模板数据等。
  • 这个标签参数是触发处理器的事件。它包含元素的名称及其属性。
  • 这个structureHandler是一个特殊的对象,它允许处理器向引擎发出指令,说明它在执行处理器时应该执行的操作。

使用structureHandler

这个标签传递给process(...)不变对象。因此,例如,无法直接修改标签对象本身。相反,structureHandler应该用。

例如,让我们看看如何读取特定标签属性,取消转义,并将其保存在变量中,然后从标记中删除该属性:

  1. // Obtain the attribute value
  2. String attributeValue = tag.getAttributeValue(attributeName);
  3. // Unescape the attribute value
  4. attributeValue =
  5. EscapedAttributeUtils.unescapeAttribute(context.getTemplateMode(), attributeValue);
  6. // Instruct the structureHandler to remove the attribute from the tag
  7. structureHandler.removeAttribute(attributeName);
  8. ... // do something with that attributeValue

请注意,上面的代码只是为了展示一些属性管理概念—在大多数处理器中,我们不需要手动执行“get value+unaway+Remove”操作,因为所有操作都将由扩展的超类处理,如AbstractAttributeTagProcessor.

上面我们只看到其中一个操作structureHandler…有一个结构处理程序对于Thymeleaf中的每种处理器,以及用于元素标签处理器实现IElementTagStructureHandler接口,如下所示:

  1. public interface IElementTagStructureHandler {
  2. public void reset();
  3. public void setLocalVariable(final String name, final Object value);
  4. public void removeLocalVariable(final String name);
  5. public void setAttribute(final String attributeName, final String attributeValue);
  6. public void setAttribute(final String attributeName, final String attributeValue,
  7. final AttributeValueQuotes attributeValueQuotes);
  8. public void replaceAttribute(final AttributeName oldAttributeName,
  9. final String attributeName, final String attributeValue);
  10. public void replaceAttribute(final AttributeName oldAttributeName,
  11. final String attributeName, final String attributeValue,
  12. final AttributeValueQuotes attributeValueQuotes);
  13. public void removeAttribute(final String attributeName);
  14. public void removeAttribute(final String prefix, final String name);
  15. public void removeAttribute(final AttributeName attributeName);
  16. public void setSelectionTarget(final Object selectionTarget);
  17. public void setInliner(final IInliner inliner);
  18. public void setTemplateData(final TemplateData templateData);
  19. public void setBody(final String text, final boolean processable);
  20. public void setBody(final IModel model, final boolean processable);
  21. public void insertBefore(final IModel model); // cannot be processable
  22. public void insertImmediatelyAfter(final IModel model, final boolean processable);
  23. public void replaceWith(final String text, final boolean processable);
  24. public void replaceWith(final IModel model, final boolean processable);
  25. public void removeElement();
  26. public void removeTags();
  27. public void removeBody();
  28. public void removeAllButFirstChild();
  29. public void iterateElement(final String iterVariableName,
  30. final String iterStatusVariableName,
  31. final Object iteratedObject);
  32. }

在这里,我们可以看到处理器可以要求模板引擎执行的所有操作。方法名称是非常清楚的(它们有javadoc),但是非常简短:

  • setLocalVariable(...)/removeLocalVariable(...)将向模板执行中添加一个局部变量。这,这个局部变量在当前事件的其余执行过程中,以及在其所有的执行过程中,都可以访问体体(即直至其相应的封闭标签)
  • setAttribute(...)将一个新属性添加到具有指定值的标记中(可能还会添加周围引号的类型)。如果属性已经存在,则其值将被替换。
  • replaceAttribute(...)用新属性替换现有属性,在属性中取代其位置(例如,包括其周围的空白)。
  • removeAttribute(...)从标记中移除属性。
  • setSelectionTarget(...)修改要被视为选择目标,即选择表达式 (*{...})将被执行。在标准方言中,选择目标通常通过th:object属性,但是自定义处理器也可以这样做。注意选择目标具有与局部变量相同的作用域,因此只能在所处理元素的正文中访问。
  • setInliner(...)修改内衬用于处理所有文本节点(IText事件)出现在正在处理的元素的主体中。这是th:inline属性以启用内衬在任何指定模式中(text, javascript(等)
  • setTemplateData(...)修改有关实际正在处理的模板的元数据。插入片段时,引擎可以知道正在处理的特定片段的数据,以及嵌套的完整片段堆栈。
  • setBody(...)用传递的文本或模型替换正在处理的元素的所有主体(事件序列=标记片段)。这就是方法,例如th:text/th:utext工作。注意,指定的替换文本或模型可以设置为可加工是否执行,取决于我们是否要执行任何可能与它们相关联的处理器。如属th:utext="${var}",例如,替换设置为非加工,以避免执行任何可能由${var}作为模板的一部分。
  • insertBefore(...)/insertImmediatelyAfter(...)允许模型(标记片段)的规范出现在立马在标签被处理之后。请注意insertImmediatelyAfter手段在处理标记之后(因此,作为元素主体的第一部分)而不是在这里打开的整个元素之后,并在某个地方关闭一个关闭标记。.
  • replaceWith(...)允许当前元素(整个元素)被指定为参数的文本或模型替换。
  • removeElement()/removeTags()/removeBody()/removeAllButFirstChild()允许处理器分别删除整个元素(包括其主体),只有已执行的标记(打开+关闭),而不是主体,只有主体,而不是包装标记,最后,除第一个子元素之外,所有标记的子元素都要删除。注意,所有这些选项基本上反映了可以在th:remove属性。
  • iterateElement(...)允许当前元素(包括主体)被迭代的次数与iteratedObject(这通常是Collection, Map, Iterator或数组)。另外两个参数将用于指定用于迭代元素和Status变量的变量的名称。

抽象实现IElementTagProcessor

Thymeleaf提供了两个基本实现IElementTagProcessor处理器可能为了方便而实现:

  • org.thymeleaf.processor.element.AbstractElementTagProcessor,用于通过元素名称匹配元素事件的处理器(即不查看属性)。
  • org.thymeleaf.processor.element.AbstractAttributeTagProcessor,用于通过其属性之一匹配元素事件的处理器(也可选择元素名称)。

元素模型处理器:IElementModelProcessor

元素模型处理器以IModel对象,该对象包含对此类元素及其内容建模的完整事件序列。这个IElementModelProcessor非常类似于上面看到的标签处理器:

  1. public interface IElementModelProcessor extends IElementProcessor {
  2. public void process(
  3. final ITemplateContext context,
  4. final IModel model,
  5. final IElementModelStructureHandler structureHandler);
  6. }

请注意此接口是如何扩展的。IElementProcessor,以及如何process(...)方法包含的结构与标记处理器中的结构相同,标签带着模型当然:

  • process(...)回报void…将在模型structureHandler不是通过归还任何东西。
  • context包含执行上下文:变量等。
  • 模型是对正在执行处理器的整个元素建模的事件序列。该模型可以直接从处理器修改。
  • structureHandler允许指示引擎执行模型修改以外的操作(例如设置局部变量)。

读取和修改模型

这个IModel对象作为参数传递给process()方法是可变模型,因此它允许对其进行任何修改(模型是可变的,事件比如标签是不变的)。例如,我们可能需要修改它,以便用具有相同内容的注释替换其正文中的每个文本节点:

  1. final IModelFactory modelFactory = context.getModelFactory();
  2. int n = model.size();
  3. while (n-- != 0) {
  4. final ITemplateEvent event = model.get(n);
  5. if (event instanceof IText) {
  6. final IComment comment =
  7. modelFactory.createComment(((IText)event).getText());
  8. model.insert(n, comment);
  9. model.remove(n + 1);
  10. }
  11. }

还请注意,IModel接口包括accept(IModelVisitor visitor)方法,用于遍历整个模型以查找特定节点或相关数据,使用来访者模式。

使用structureHandler

类似于标签处理器,模型处理器被传递给结构处理程序对象,该对象允许它们指示引擎执行任何不能通过直接对IModel model对象本身。这些结构处理程序实现的接口比用于标记处理器的接口小得多,它是IElementModelStructureHandler:

  1. public interface IElementModelStructureHandler {
  2. public void reset();
  3. public void setLocalVariable(final String name, final Object value);
  4. public void removeLocalVariable(final String name);
  5. public void setSelectionTarget(final Object selectionTarget);
  6. public void setInliner(final IInliner inliner);
  7. public void setTemplateData(final TemplateData templateData);
  8. }

很容易看出这是标记处理器的子集。那里的几种方法也是这样工作的:

  • setLocalVariable(...)/removeLocalVariable(...)用于添加/删除在模型执行期间可用的局部变量(在当前处理器执行之后)。
  • setSelectionTarget(...)用于修改选择目标在模型执行期间应用。
  • setInliner(...)因为设置了内衬。
  • setTemplateData(...)用于设置有关正在处理的模板的元数据。

抽象实现IElementModelProcessor

Thymeleaf提供了两个基本实现IElementModelProcessor处理器可能为了方便而实现:

  • org.thymeleaf.processor.element.AbstractElementModelProcessor,用于通过元素名称匹配元素事件的处理器(即不查看属性)。
  • org.thymeleaf.processor.element.AbstractAttributeModelProcessor,用于通过其属性之一匹配元素事件的处理器(也可选择元素名称)。

模板启动/结束处理器:ITemplateBoundariesProcessor

模板边界处理器是在模板启动模板端在模板处理期间触发的事件。它们允许在模板处理操作的开始或结束时执行任何类型的资源初始化或处理。请注意,这些事件是只为第一级模板触发。,而不是针对可能被解析和/或包含在正在处理的模板中的每个片段。

这个ITemplateBoundariesProcessor接口如下所示:

  1. public interface ITemplateBoundariesProcessor extends IProcessor {
  2. public void processTemplateStart(
  3. final ITemplateContext context,
  4. final ITemplateStart templateStart,
  5. final ITemplateBoundariesStructureHandler structureHandler);
  6. public void processTemplateEnd(
  7. final ITemplateContext context,
  8. final ITemplateEnd templateEnd,
  9. final ITemplateBoundariesStructureHandler structureHandler);
  10. }

这一次,接口提供了两个process*(...)方法,一个用于模板启动还有另一个模板端事件。他们的签名与其他签名的图案相同。process(...)方法,接收上下文、事件对象和结构处理程序。结构处理程序,在本例中,它实现了一个非常简单的ITemplateBoundariesStructureHandler接口:

  1. public interface ITemplateBoundariesStructureHandler {
  2. public void reset();
  3. public void setLocalVariable(final String name, final Object value);
  4. public void removeLocalVariable(final String name);
  5. public void setSelectionTarget(final Object selectionTarget);
  6. public void setInliner(final IInliner inliner);
  7. public void insert(final String text, final boolean processable);
  8. public void insert(final IModel model, final boolean processable);
  9. }

我们可以看到,除了通常管理局部变量、选择目标和内联的方法之外,我们还可以使用结构处理程序插入文本或模型,在这种情况下,文本或模型将出现在结果的最开始或最末尾(取决于正在处理的事件)。

其他处理器

Thymeleaf 3.0还允许声明处理器的其他事件,每个事件实现相应的接口:

  • 文本事件:接口ITextProcessor
  • 评语事件:接口ICommentProcessor
  • CDATA科事件:接口ICDATASectionProcessor
  • DOCTYPE条款事件:接口IDocTypeProcessor
  • XML声明事件:接口IXMLDeclarationProcessor
  • 处理指令事件:接口IProcessingInstructionProcessor

它们看起来都很像(这是文本事件的一个):

  1. public interface ITextProcessor extends IProcessor {
  2. public void process(
  3. final ITemplateContext context,
  4. final IText text,
  5. final ITextStructureHandler structureHandler);
  6. }

与所有其他模式相同process(...)方法:上下文、事件、结构处理程序。这些结构处理程序非常简单,就像这样(同样,用于文本事件的处理程序):

  1. public interface ITextStructureHandler {
  2. public void reset();
  3. public void setText(final CharSequence text);
  4. public void replaceWith(final IModel model, final boolean processable);
  5. public void removeText();
  6. }

3创造我们自己的方言

本指南和本指南未来章节中所示示例的源代码可在外Thyme GitHub储存库.

3.1.Thymeland足球联盟网站

足球在蒂梅兰是一项很受欢迎的运动。1…每个赛季都有一个10支球队的联赛,它的组织者刚刚要求我们为它创建一个名为“外Thyme”的网站。

这个网站将非常简单:只是一张桌子,上面有:

  • 队名。
  • 他们赢得了多少场比赛,平局还是输了,以及所获得的总分。
  • 一句话解释了他们在积分榜上的地位是否能让他们有资格参加明年的更高级别的比赛,否则就意味着他们被降级到了地方联赛。

在联赛排行榜上方,一个横幅将显示最近几场比赛结果的标题,还有一个明显可见的横幅,提醒用户星期日是比赛日,因此他们应该去体育场,而不是上网。

教程:扩展Thymeleaf - 图2

Thyme表外

我们将使用HTML 5、SpringMVC和SpringStandard方言作为我们的应用程序,我们将通过创建一个score方言,包括:

  • A score:remarkforposition属性,该属性为表中的“备注”列输出国际化文本。这篇文章应该解释一下球队在积分榜上的位置是能胜任世界冠军联赛、欧洲杯还是降级到区域联赛。
  • A score:classforposition属性,它根据团队的备注为表行建立一个css类:世界冠军联赛的蓝色背景,欧洲杯的绿色,降级的红色。
  • A score:headlines标记,用于绘制顶部的黄色框和最近匹配的结果。此标记应该支持具有随机值的Order属性(用于显示随机选择的匹配)和latest(默认情况下,只显示最后一次匹配)。
  • A score:match-day-today属性,该属性可以添加到联赛表标题中,以便(有条件地,如果是周日)添加一个横幅,警告用户今天是比赛日。

因此,我们的标记将如下所示,同时利用两者thscore属性:

(请注意,我们在表中添加了第二行和第三行,被解析器级注释包围。这样,当在浏览器中直接打开时,我们的模板就能很好地显示为原型。)

3.2.按团队位置更改CSS类

我们将开发的第一个属性处理器是ClassForPositionAttributeTagProcessor,它将实现为Thymeleaf提供的一个方便抽象类的子类。AbstractAttributeTagProcessor.

这个抽象类是所有标记处理器(即对其起作用的处理器)的基础。标签事件与否模型)基于该标记中特定属性的存在而选择的匹配(即选择执行)。在这种情况下,score:classforposition.

我们的想法是,我们将使用这个处理器将一个新值设置为class标记的属性score:classforposition住在。

让我们看看我们的代码:

基本的逻辑流很容易看到和理解:获取属性的值,使用它来计算我们需要的东西,最后使用structureHandler指示引擎所需的修改。

需要注意的是,我们正在创建这个属性,它具有执行用标准语法编写的表达式的能力(由两个标准SpringStandard方言)。这就是,设置值的能力,比如${var}, #{messageKey},条件等。看看我们是如何在模板中使用这个的:

  1. <tr th:each="t : ${teams}" score:classforposition="${tStat.count}">

为了计算这些表达式(也称为Thymeleaf标准表达式)我们需要首先获得标准表达式分析器,然后解析属性值,最后执行解析的表达式:

  1. final IStandardExpressionParser parser =
  2. StandardExpressions.getExpressionParser(configuration);
  3. final IStandardExpression expression = parser.parseExpression(context, attributeValue);
  4. final Integer position = (Integer) expression.execute(context);

我们使用structureHandler若要向主机标记添加新属性,请记住标签对象是不可变的):

  1. if (newValue != null) {
  2. String currentClass = tag.getAttribute("class").getValue();
  3. if (currentClass != null) {
  4. structureHandler.setAttribute("class", currentClass + " " + newValue);
  5. } else {
  6. structureHandler.setAttribute("class", newValue);
  7. }
  8. }

最后,请注意转义文本和属性是我们的责任。,但在这种情况下,我们知道newValue变量,它们不需要转义,因此为了简单起见,我们跳过了该操作。

3.3.显示国际化的注释

接下来要做的是创建一个能够显示注释文本的属性处理器。这将非常类似于ClassForPositionAttrProcessor,但是有几个重要的区别:

  • 我们将不为宿主标记中的属性设置值,而是以相同的方式设置标记的文本主体(内容)th:text属性有。
  • 我们需要从代码中访问消息外部化(国际化)系统,以便能够显示与所选区域设置对应的文本。

我们会用同样的AbstractAttributeTagProcessor基类。这将是我们的代码:

访问i18n消息

请注意,我们使用以下方法访问消息外部化系统:

  1. final String i18nMessage =
  2. context.getMessage(
  3. RemarkForPositionAttributeTagProcessor.class,
  4. "remarks." + remark.toString(),
  5. new Object[0],
  6. true);

这将调用在引擎上配置的消息解析机制,不仅传递我们感兴趣的特定键及其参数(在本例中为None),而且还传递另外两条信息:

  • 这个起源分配给信息:RemarkForPositionAttributeTagProcessor.class
  • 是否缺席消息表示应使用(true)

消息解析是扩展点在Thymeleaf(IMessageResolver因此,如何处理这些参数取决于所使用的具体实现。非支持Spring的应用程序中的默认实现(StandardMessageResolver)将做以下工作:

  • 先找.properties与模板文件+区域设置名称相同的文件。所以如果模板是/views/main.html地点是gl_ES,它会寻找/views/main_gl_ES.properties,然后/views/main_gl.properties最后/views/main.properties.
  • 如果找不到,则使用起源类(本可以指定该类)。null)和寻找.properties类路径中的文件,其中指定了类的名称(处理器自己的类):classpath:thymeleafexamples/extrathyme/dialects/score/RemarkForPositionAttributeTagProcessor_gl_ES.properties等。这允许组件化或者是处理器和方言,以及它们的整套i18n资源包。.jar档案。
  • 如果没有找到这些,请查看缺席消息表示旗子。如果false,简单地返回null…如果true,创建某种文本,允许开发人员或用户快速识别缺少i18n资源的事实:??remarks.rel_gl_ES??.

(请注意,在启用Spring的应用程序中,此消息解析机制将默认替换为Spring自己的机制,其基础是MessageSource)在SpringApplication上下文中声明的bean。)

HTML转义内容

此外,在这个处理器中,我们使用HtmlEscape类的逃逸图书馆,在整个Thymeleaf中用于此目的:

  1. structureHandler.setBody(HtmlEscape.escapeHtml5(i18nMessage), false);

3.4.我们标题的元素处理器

我们将要编写的第三个处理器是一个元素(标记)处理器。注意,我们称之为a元素标记处理器与之前的两个处理器形成对比,这两个处理器是属性标记处理器…原因是,在这种情况下,我们希望我们的处理器匹配(即选择执行)基于标签名称,而不是在它的一个属性的名称上。

对于属性标记处理器,这种标记处理器有一个优点,也有一个缺点:

  • 优点:元素可以包含多个属性,因此元素处理器可以接收更丰富和更复杂的配置参数集。
  • 缺点:浏览器不知道自定义元素/标记,因此如果您使用自定义标记开发Web应用程序,您可能不得不牺牲Thymeleaf最有趣的特性之一:静态地将模板显示为原型的能力(天然模板).

这个处理器将扩展AbstractElementTagProcessor,用于与特定属性不匹配的标记处理器的基类:

上面代码的第一个有趣部分是演示如何访问Spring的ApplicationContext以便从其中获取我们的一个bean(HeadlineRepository):

  1. final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);

而且,这个处理器不同于以前的处理器,因为我们需要创建标记由于它的执行:我们将替换原来的<score:headlines .../>标记带有<div>...</div>片段,因此我们需要使用模型工厂.

模型工厂

模型工厂是处理器(以及其他结构,如前处理器、后处理器等)可用的特殊对象。的新实例。事件模型(模板的片段),以及模型他们自己。

因此,它是创建新标记的工具,正如我们在上面的代码中所看到的:

  1. final IModelFactory modelFactory = context.getModelFactory();
  2. final IModel model = modelFactory.createModel();
  3. model.add(modelFactory.createOpenElementTag("div", "class", "headlines"));
  4. model.add(modelFactory.createText(HtmlEscape.escapeHtml5(headlineText)));
  5. model.add(modelFactory.createCloseElementTag("div"));

请注意如何创建标记事件。一次一个事件,以及如何打开和关闭相同的标签。div元素必须以正确的顺序单独创建。这是因为模特们事件序列而不是文档对象模型(DOM)中的节点。

模型工厂提供了一套非常完整的方法来创建所有类型的事件:标记、文本、docypes…。以及修改标记中的属性的有用方法(通过创建一个新的标签实例,因为它们是不可变的),例如:

  1. final IOpenElemenTag newTag = modelFactory.setAttribute(tag, "class", "newvalue");

而且,模型工厂能够创建IModel实例(如modelFactory.createModel()),来自单个现有事件,也来自我们希望通过以下方式将其转换为相应的事件序列的标记。解析它:

  1. final IModel model =
  2. modelFactory.parse(
  3. context.getTemplateData(),
  4. "<div class='headlines'>Some headlines</div>");

3.5.我们的“今天比赛日”横幅的模型处理器

我们将在我们的方言中包含的最后一个处理器的性质与我们迄今所见的不同:它是一个模型处理机,而不是标签处理器.

正如前面一节所提到的,模型处理器不对特定的标记事件执行,而是在完整的事件序列上执行(即模型),它包含它们所匹配的整个元素。

所以如果我们有一个模型处理器<p>带有属性的标记score:matcher,以及模板的片段,例如:

  1. <p score:matcher="whatever">
  2. This is some body
  3. </p>

那,那个模型处理机将收到作为其doProcess()方法ANIModel包含3项活动:<p score:matcher="whatever">(开放标签),\n This is some body\n(案文)和</p>(紧贴标签)。

因此,回到我们的需求:我们想要一个模型处理器匹配scrore:match-day-today,我们可以将其应用于联赛表标题,并使其在此标题下面显示一个横幅,警告用户星期日是比赛日:

  1. <h2 score:match-day-today th:text="#{title.leaguetable(${execInfo.now.time})}">
  2. League table for 07 July 2011
  3. </h2>

注意,我们不需要这个值score:match-day-today属性,所以我们可以忽略它。我们的代码如下所示:

首先要注意的是,我们正在对属性正在使用的位置执行检查:我们只允许它在容器中使用class="leaguetable"…所以我们checkPositionInMarkup(...)方法使用元素堆栈以了解必须处理的标记列表,以便处理当前的标记。

另外,关于创建新横幅元素的方式(<h4>)注意我们正在做的是如何修改模型作为参数传递给doProcess(...)…没有创建新的模型对象:

  1. final IModelFactory modelFactory = context.getModelFactory();
  2. model.add(modelFactory.createOpenElementTag("h4", "class", "matchday")); //
  3. model.add(modelFactory.createText("Today is MATCH DAY!"));
  4. model.add(modelFactory.createCloseElementTag("h4"));

3.6.宣告一切:方言

为了完成我们的方言,我们需要采取的最后一步是,当然,方言类本身。

如上一节所示,方言可能实现不同的接口,这取决于它们为模板引擎提供了什么。在这种情况下,我们的方言只提供处理器,因此它将实现IProcessorDialect.

实际上,我们将扩展一个抽象的方便实现,它将简化接口的实现:AbstractProcessorDialect:

  1. public class ScoreDialect extends AbstractProcessorDialect {
  2. private static final String DIALECT_NAME = "Score Dialect";
  3. public ScoreDialect() {
  4. // We will set this dialect the same "dialect processor" precedence as
  5. // the Standard Dialect, so that processor executions can interleave.
  6. super(DIALECT_NAME, "score", StandardDialect.PROCESSOR_PRECEDENCE);
  7. }
  8. /*
  9. * Two attribute processors are declared: 'classforposition' and
  10. * 'remarkforposition'. Also one element processor: the 'headlines'
  11. * tag.
  12. */
  13. public Set<IProcessor> getProcessors(final String dialectPrefix) {
  14. final Set<IProcessor> processors = new HashSet<IProcessor>();
  15. processors.add(new ClassForPositionAttributeTagProcessor(dialectPrefix));
  16. processors.add(new RemarkForPositionAttributeTagProcessor(dialectPrefix));
  17. processors.add(new HeadlinesElementTagProcessor(dialectPrefix));
  18. processors.add(new MatchDayTodayModelProcessor(dialectPrefix));
  19. // This will remove the xmlns:score attributes we might add for IDE validation
  20. processors.add(new StandardXmlNsTagProcessor(TemplateMode.HTML, dialectPrefix));
  21. return processors;
  22. }
  23. }

一旦我们的方言被创建,我们将需要将它添加到模板引擎对象中,以便使用它。作为一个启用Spring的应用程序,我们将修改声明的模板引擎bean:

  1. @Bean
  2. public SpringTemplateEngine templateEngine(){
  3. SpringTemplateEngine templateEngine = new SpringTemplateEngine();
  4. templateEngine.setTemplateResolver(templateResolver());
  5. templateEngine.addDialect(new ScoreDialect());
  6. return templateEngine;
  7. }

注意,addDialect(...)调用那里将把分数方言添加到默认情况下已在SpringTemplateEngine春标准方言。

就这样!我们的方言已经准备好了,我们的排名表将以我们想要的方式显示。


  1. 当然,欧洲足球;-)