这篇文章是“Thymeleaf扩展“的后续教程。 本文中的代码来自相同的示例应用程序,您可以从其GitHub仓库查看或下载该应用程序。

1. 对 hello 方言改进

到目前为止,HelloDialect允许将此变为:

  1. <p hello:sayto="World">Hi ya!</p>

它工作得很好,但这里需要添加一些附加功能,作为学习演示。 例如:

  • 允许Spring EL表达式作为属性值,就像Spring Thymeleaf Dialect中的大多数标签一样。 例如:
  1. hello:sayto="${user.name}"

国际化输出:对英语说“Hello”,对西班牙语说“Hola”,对葡萄牙语说“Olá”等。上面已经准备好了所有的工作,这里希望能够创建一个名为“saytoplanet”的新属性,并向太阳系中的所有行星问候,其模板如下:

  1. <ul>
  2. <li th:each="planet : ${planets}" hello:saytoplanet="${planet}">Hello Planet!</li>
  3. </ul>

它由一个Spring MVC控制器支持,该控制器包括所有这些行星作为一个叫作planets的模型属性:

  1. @Controller
  2. public class SayHelloController {

  3. public SayHelloController() {

  4. super();

  5. }

  6. @ModelAttribute("planets")

  7. public List<String> populatePlanets() {

  8. return Arrays.asList(new String[] {

  9. "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"

  10. });

  11. }

  12. @RequestMapping({"/","/sayhello"})

  13. public String showSayHello() {

  14. return "sayhello";

  15. }

  16. }

2. 为方言添加一个新的处理器

这里做的第一件事是添加一个新的处理器到现有的HelloDialect中。 为此,需要修改方言的getProcessors()方法,以包含新的SayToPlanetAttrProcessor类:

  1. public class HelloDialect extends AbstractProcessorDialect {
  2. ...
  3. /*
  4. * Initialize the dialect's processors.
  5. *
  6. * Note the dialect prefix is passed here because, although we set
  7. * "hello" to be the dialect's prefix at the constructor, that only
  8. * works as a default, and at engine configuration time the user
  9. * might have chosen a different prefix to be used.
  10. */
  11. public Set<IProcessor> getProcessors(final String dialectPrefix) {
  12. final Set<IProcessor> processors = new HashSet<IProcessor>();
  13. processors.add(new SayToAttributeTagProcessor(dialectPrefix));
  14. processors.add(new SayToPlanetAttributeTagProcessor(dialectPrefix));
  15. return processors;
  16. }
  17. ...
  18. }

3. 使用表达式作为属性值

现在想要在新处理器中添加解析和执行表达式的能力,就像在StandardSpringStandard方言中所做的那样,下面是Thymeleaf标准表达式:

  • ${…} - Spring EL变量表达式。
  • #{…} - 消息的外部化。
  • @{…} - 链接规范。
  • (cond)? (then) : (else) - 条件/默认表达式。 为了实现这一点,将使用标准表达式解析器,它将解析属性值为一个可执行的表达式对象:
  1. public class SayToPlanetAttributeTagProcessor extends AbstractAttributeTagProcessor {
  2.  
  3. private static final String ATTR_NAME = "saytoplanet";
  4. private static final int PRECEDENCE = 10000;
  5.  
  6. private static final String SAYTO_PLANET_MESSAGE = "msg.helloplanet";
  7.  
  8. public SayToPlanetAttributeTagProcessor(final String dialectPrefix) {
  9. super(
  10. TemplateMode.HTML, // This processor will apply only to HTML mode
  11. dialectPrefix, // Prefix to be applied to name for matching
  12. null, // No tag name: match any tag name
  13. false, // No prefix to be applied to tag name
  14. ATTR_NAME, // Name of the attribute that will be matched
  15. true, // Apply dialect prefix to attribute name
  16. PRECEDENCE, // Precedence (inside dialect's precedence)
  17. true); // Remove the matched attribute afterwards
  18. }
  19.  
  20. protected void doProcess(
  21. final ITemplateContext context, final IProcessableElementTag tag,
  22. final AttributeName attributeName, final String attributeValue,
  23. final IElementTagStructureHandler structureHandler) {
  24.  
  25. /*
  26. * In order to evaluate the attribute value as a Thymeleaf Standard Expression,
  27. * we first obtain the parser, then use it for parsing the attribute value into
  28. * an expression object, and finally execute this expression object.
  29. */
  30. final IEngineConfiguration configuration = context.getConfiguration();
  31.  
  32. final IStandardExpressionParser parser =
  33. StandardExpressions.getExpressionParser(configuration);
  34.  
  35. final IStandardExpression expression = parser.parseExpression(context, attributeValue);
  36.  
  37. final String planet = (String) expression.execute(context);
  38. /*
  39. * Set the salutation as the body of the tag, HTML-escaped and
  40. * non-processable (hence the 'false' argument)
  41. */
  42. structureHandler.setBody("Hello, planet " + planet, false);
  43. }
  44. }

请注意,正如在前一篇文章中所做的,扩展AbstractAttributeTagProcessor抽象类。

4. 添加国际化

现在要将属性处理器返回的消息国际化。这意味着替换这个仅是英文的消息构建代码:

  1. "Hello, planet " + planet;

从外部化的字符串构建的消息,x必须以某种方式从代码中获得。 上下文对象(ITemplateContext)提供需要的东西:

  1. public String getMessage(
  2. final Class<?> origin,
  3. final String key,
  4. final Object[] messageParameters,
  5. final boolean useAbsentMessageRepresentation);

它的参数有以下含义:

  • origin - 用于消息解析的起源类。 从处理器调用时,通常是处理器类本身。
  • key - 要检索的消息的键。
  • messageParameters - 要应用于请求的消息的参数。
  • origin - 在消息不存在或不存在的情况下是否应该返回缺少指定消息表示。 所以下面用它来实现一些国际化。 首先,需要一些.properties文件,如西班牙语的SayToPlanetAttributeTagProcessor_es.properties:
  1. msg.helloplanetHola, planeta {0}!

葡萄牙语的SayToPlanetAttributeTagProcessor_pt.properties:

  1. msg.helloplanet=Olá, planeta {0}!

等等,其它语言。

现在将不得不修改SayToPlanetAttributeTagProcessor处理器类来使用这些消息:

  1. protected void doProcess(
  2. final ITemplateContext context, final IProcessableElementTag tag,
  3. final AttributeName attributeName, final String attributeValue,
  4. final IElementTagStructureHandler structureHandler) {
  5.  
  6. /*
  7. * In order to evaluate the attribute value as a Thymeleaf Standard Expression,
  8. * we first obtain the parser, then use it for parsing the attribute value into
  9. * an expression object, and finally execute this expression object.
  10. */
  11. final IEngineConfiguration configuration = context.getConfiguration();
  12.  
  13. final IStandardExpressionParser parser =
  14. StandardExpressions.getExpressionParser(configuration);
  15.  
  16. final IStandardExpression expression = parser.parseExpression(context, attributeValue);
  17.  
  18. final String planet = (String) expression.execute(context);
  19.  
  20. /*
  21. * This 'getMessage(...)' method will first try to resolve the message
  22. * from the configured Spring Message Sources (because this is a Spring
  23. * -enabled application).
  24. *
  25. * If not found, it will try to resolve it from a classpath-bound
  26. * .properties with the same name as the specified 'origin', which
  27. * in this case is this processor's class itself. This allows resources
  28. * to be packaged if needed in the same .jar files as the processors
  29. * they are used in.
  30. */
  31. final String i18nMessage =
  32. context.getMessage(
  33. SayToPlanetAttributeTagProcessor.class,
  34. SAYTO_PLANET_MESSAGE,
  35. new Object[] {planet},
  36. true);
  37.  
  38. /*
  39. * Set the computed message as the body of the tag, HTML-escaped and
  40. * non-processable (hence the 'false' argument)
  41. */
  42. structureHandler.setBody(HtmlEscape.escapeHtml5(i18nMessage), false);
  43.  
  44. }protected void doProcess(
  45. final ITemplateContext context, final IProcessableElementTag tag,
  46. final AttributeName attributeName, final String attributeValue,
  47. final IElementTagStructureHandler structureHandler) {
  48.  
  49. /*
  50. * In order to evaluate the attribute value as a Thymeleaf Standard Expression,
  51. * we first obtain the parser, then use it for parsing the attribute value into
  52. * an expression object, and finally execute this expression object.
  53. */
  54. final IEngineConfiguration configuration = context.getConfiguration();
  55.  
  56. final IStandardExpressionParser parser =
  57. StandardExpressions.getExpressionParser(configuration);
  58.  
  59. final IStandardExpression expression = parser.parseExpression(context, attributeValue);
  60.  
  61. final String planet = (String) expression.execute(context);
  62.  
  63. /*
  64. * This 'getMessage(...)' method will first try to resolve the message
  65. * from the configured Spring Message Sources (because this is a Spring
  66. * -enabled application).
  67. *
  68. * If not found, it will try to resolve it from a classpath-bound
  69. * .properties with the same name as the specified 'origin', which
  70. * in this case is this processor's class itself. This allows resources
  71. * to be packaged if needed in the same .jar files as the processors
  72. * they are used in.
  73. */
  74. final String i18nMessage =
  75. context.getMessage(
  76. SayToPlanetAttributeTagProcessor.class,
  77. SAYTO_PLANET_MESSAGE,
  78. new Object[] {planet},
  79. true);
  80.  
  81. /*
  82. * Set the computed message as the body of the tag, HTML-escaped and
  83. * non-processable (hence the 'false' argument)
  84. */
  85. structureHandler.setBody(HtmlEscape.escapeHtml5(i18nMessage), false);
  86.  
  87. }

接下来看看使用西班牙语区域设置执行模板的结果:

  • ¡Hola, planeta Mercury!
  • ¡Hola, planeta Venus!
  • ¡Hola, planeta Earth!
  • ¡Hola, planeta Mars!
  • ¡Hola, planeta Jupiter!
  • ¡Hola, planeta Saturn!
  • ¡Hola, planeta Uranus!
  • ¡Hola, planeta Neptune!