再次说声你好!用另外5分钟进一步扩展Thymeleaf
本文是以下文章的延续:“Say Hello! 在5分钟内扩展Thymeleaf”并建议在此之后阅读。本文中的代码来自同一个示例应用程序,你可以查看或下载:其 GitHub 仓库.
我们的“hello”方言的一些改进
到目前为止,我们的HelloDialect
允许我们将这样的内容:
<p hello:sayto="World">Hi ya!</p>
…转换为:
<p>Hello World!</p>
它运行得很好……但我们想添加一些不错的附加功能。例如:
- 允许将Spring EL表达式作为属性值,就像在大多数Spring Thymeleaf方言中的标签一样。例如:
hello:sayto="${user.name}"
- 国际化输出:英文显示Hello,西班牙文显示Hola,葡萄牙语显示Olá等等。
我们需要所有这些功能是因为我们希望创建一个名为“saytoplanet
”的新属性,并以类似如下的模板向太阳系中所有的行星问好:
<ul>
<li th:each="planet : ${planets}" hello:saytoplanet="${planet}">Hello Planet!</li>
</ul>
…由一个Spring MVC控制器支持,这个控制器将所有这些行星包含为一个名为planets
:
@Controller
public class SayHelloController {
public SayHelloController() {
super();
}
@ModelAttribute("planets")
public List<String> populatePlanets() {
return Arrays.asList(new String[] {
"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"
});
}
@RequestMapping({"/","/sayhello"})
public String showSayHello() {
return "sayhello";
}
}
的模型属性。
我们想要做的第一件事是在我们现有的处理器基础上添加一个新的HelloDialect
。为此,我们在方言的getProcessors()
方法中加入新的SayToPlanetAttrProcessor
类:
public class HelloDialect extends AbstractProcessorDialect {
...
/*
* Initialize the dialect's processors.
*
* Note the dialect prefix is passed here because, although we set
* "hello" to be the dialect's prefix at the constructor, that only
* works as a default, and at engine configuration time the user
* might have chosen a different prefix to be used.
*/
public Set<IProcessor> getProcessors(final String dialectPrefix) {
final Set<IProcessor> processors = new HashSet<IProcessor>();
processors.add(new SayToAttributeTagProcessor(dialectPrefix));
processors.add(new SayToPlanetAttributeTagProcessor(dialectPrefix));
return processors;
}
...
}
使用表达式作为属性值
现在我们希望为我们的新处理器添加解析并执行表达式的能力,这与我们在标准和SpringStandard方言中可以做到的方式相同,也就是说,Thymeleaf标准表达式:
${...}
Spring EL变量表达式。#{...}
消息外化。@{...}
链接规范。(cond)? (then) : (else)
条件/默认表达式。- 等等……
为了实现这一点,我们将使用标准表达式解析器,它会将属性值解析为可执行的表达式对象的直接访问:
public class SayToPlanetAttributeTagProcessor extends AbstractAttributeTagProcessor {
private static final String ATTR_NAME = "saytoplanet";
private static final int PRECEDENCE = 10000;
private static final String SAYTO_PLANET_MESSAGE = "msg.helloplanet";
public SayToPlanetAttributeTagProcessor(final String dialectPrefix) {
super(
TemplateMode.HTML, // This processor will apply only to HTML mode
dialectPrefix, // Prefix to be applied to name for matching
null, // No tag name: match any tag name
false, // No prefix to be applied to tag name
ATTR_NAME, // Name of the attribute that will be matched
true, // Apply dialect prefix to attribute name
PRECEDENCE, // Precedence (inside dialect's precedence)
true); // Remove the matched attribute afterwards
}
protected void doProcess(
final ITemplateContext context, final IProcessableElementTag tag,
final AttributeName attributeName, final String attributeValue,
final IElementTagStructureHandler structureHandler) {
/*
* In order to evaluate the attribute value as a Thymeleaf Standard Expression,
* we first obtain the parser, then use it for parsing the attribute value into
* an expression object, and finally execute this expression object.
*/
final IEngineConfiguration configuration = context.getConfiguration();
final IStandardExpressionParser parser =
StandardExpressions.getExpressionParser(configuration);
final IStandardExpression expression = parser.parseExpression(context, attributeValue);
final String planet = (String) expression.execute(context);
/*
* Set the salutation as the body of the tag, HTML-escaped and
* non-processable (hence the 'false' argument)
*/
structureHandler.setBody("Hello, planet " + planet, false);
}
}
注意,正如我们在前一篇文章中所做的那样,我们正在扩展AbstractAttributeTagProcessor
方便抽象类。
添加国际化支持
现在我们希望对我们属性处理器返回的消息进行国际化。这意味着将替换这段仅支持英文的消息构建代码:
"Hello, planet " + planet;
…替换为我们必须通过某种方式从代码中获取的外部字符串消息。上下文对象(ITemplateContext
)提供了我们需要的功能:
public String getMessage(
final Class<?> origin,
final String key,
final Object[] messageParameters,
final boolean useAbsentMessageRepresentation);
它的参数有以下含义:
origin
the 起源类,用于消息解析。调用自处理器时,通常就是处理器自身类。key
要检索消息的键。messageParameters
应用于所请求消息的参数。useAbsentMessageRepresentation
是否应返回缺失消息表示如果消息不存在的情况下是否返回
所以让我们使用这项功能来实现国际化。首先我们需要一些.properties
文件,比如SayToPlanetAttributeTagProcessor_es.properties
西班牙语版:
msg.helloplanet=¡Hola, planeta {0}!
SayToPlanetAttributeTagProcessor_pt.properties
葡萄牙语版:
msg.helloplanet=Olá, planeta {0}!
…等等。
现在我们必须修改SayToPlanetAttributeTagProcessor
处理器类来使用这些消息:
protected void doProcess(
final ITemplateContext context, final IProcessableElementTag tag,
final AttributeName attributeName, final String attributeValue,
final IElementTagStructureHandler structureHandler) {
/*
* In order to evaluate the attribute value as a Thymeleaf Standard Expression,
* we first obtain the parser, then use it for parsing the attribute value into
* an expression object, and finally execute this expression object.
*/
final IEngineConfiguration configuration = context.getConfiguration();
final IStandardExpressionParser parser =
StandardExpressions.getExpressionParser(configuration);
final IStandardExpression expression = parser.parseExpression(context, attributeValue);
final String planet = (String) expression.execute(context);
/*
* This 'getMessage(...)' method will first try to resolve the message
* from the configured Spring Message Sources (because this is a Spring
* -enabled application).
*
* If not found, it will try to resolve it from a classpath-bound
* .properties with the same name as the specified 'origin', which
* in this case is this processor's class itself. This allows resources
* to be packaged if needed in the same .jar files as the processors
* they are used in.
*/
final String i18nMessage =
context.getMessage(
SayToPlanetAttributeTagProcessor.class,
SAYTO_PLANET_MESSAGE,
new Object[] {planet},
true);
/*
* Set the computed message as the body of the tag, HTML-escaped and
* non-processable (hence the 'false' argument)
*/
structureHandler.setBody(HtmlEscape.escapeHtml5(i18nMessage), false);
}
就这样!来看看使用西班牙语言环境执行我们的模板后结果:
- ¡Hola, planeta Mercury!
- ¡Hola, planeta Venus!
- ¡Hola, planeta Earth!
- ¡Hola, planeta Mars!
- ¡Hola, planeta Jupiter!
- ¡Hola, planeta Saturn!
- ¡Hola, planeta Uranus!
- ¡Hola, planeta Neptune!
给读者的练习:国际化行星名称
现在我们已经对属性处理器生成的消息应用了国际化,但是我们的行星名称仍然是英文的,因为它们都是硬编码变量,在Spring术语里,模型属性)。
那么,如何把那些行星名称也国际化?我们现在可以在该属性中使用的#{...}
表达式应该使这变得相当简单,也有一些像这样的文章中的例子“五分钟快速入门标准方言”和教程非常接近此场景。