Thymeleaf擴充套件2(Say Hello)


這篇文章是「Thymeleaf擴充套件「的後續教學。 本文中的程式碼來自相同的範例應用程式,您可以從其GitHub倉庫檢視或下載該應用程式。

1. 對 hello 方言改進

到目前為止,HelloDialect允許將此變為:

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

它工作得很好,但這裡需要新增一些附加功能,作為學習演示。 例如:

  • 允許Spring EL表示式作為屬性值,就像Spring Thymeleaf Dialect中的大多數標籤一樣。 例如:
    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";
  }

}

2. 為方言新增一個新的處理器

這裡做的第一件事是新增一個新的處理器到現有的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;
  }
  ...
}

3. 使用表示式作為屬性值

現在想要在新處理器中新增解析和執行表示式的能力,就像在StandardSpringStandard方言中所做的那樣,下面是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抽象類。

4. 新增國際化

現在要將屬性處理器返回的訊息國際化。這意味著替換這個僅是英文的訊息構建程式碼:

"Hello, planet " + planet;

從外部化的字串構建的訊息,x必須以某種方式從程式碼中獲得。 上下文物件(ITemplateContext)提供需要的東西:

public String getMessage(
            final Class<?> origin, 
            final String key, 
            final Object[] messageParameters, 
            final boolean useAbsentMessageRepresentation);

它的引數有以下含義:

  • origin - 用於訊息解析的起源類。 從處理器呼叫時,通常是處理器類本身。
  • key - 要檢索的訊息的鍵。
  • messageParameters - 要應用於請求的訊息的引數。
  • origin - 在訊息不存在或不存在的情況下是否應該返回缺少指定訊息表示。

所以下面用它來實現一些國際化。 首先,需要一些.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);

}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!