2013年12月25日水曜日

[FreeMarker] ftl名前空間のすゝめ

つい最近知ったのですが、ftlにも名前空間があります。
例えば、ある画面にマクロを定義しているFTLを複数読み込む時、そのFTL毎に名前空間を分ける、ということができます。

名前空間の使い方

これの何がいいかというと、名前空間を分けると各FTLで定義した変数名やマクロ名が衝突しなくなります。
library-a.ftlfoo というマクロを定義し、 library-b.ftl でも同名のマクロを定義し、この2つのFTLを同じ画面で読み込み、それぞれのマクロを同時に、別のマクロとして使うことができるようになります。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) John Smith. All rights reserved.</p>
</#macro>  

<#assign mail = "jsmith@acme.com">

上記のライブラリFTLを lib/library-a.ftl とします。
そして、このライブラリFTLを top.ftl で利用します。

<#inclde "/lib/library-a.ftl">

このように書くと、 copyright マクロと mail 変数がグローバルな名前空間に定義されてしまいます。
これだと、仮に top.ftl ですでに copyright という名前のマクロや mail という変数を定義していると、名前が衝突してエラーになってしまいます。(変数は同じものとして扱われます)

これだと、再利用可能なコードとは言えません。
そこで、 import という、別の構文を使ってライブラリFTLを読み込みます。

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 John Smith. All rights reserved.</p>
jsmith@acme.com
-->

import を使ってライブラリFTLを読み込むと、新しい名前空間が作成されます。
この新しい名前空間からは、ライブラリFTL内で定義された変数か、グローバルで定義された変数か、データモデルの変数(Javaのアクションクラスから渡された変数)しか見ることができません。

この新しい名前空間にある2つの変数(マクロ)に top.ftl からアクセスするには、 import 構文で as の後ろに指定された名前を使います。この場合、上のコードで示したように、 libA.copyright, libA.mail という形で library-a.ftl で定義された変数やマクロにアクセスできます。

このように import 構文を使ってFTLを読み込むと、たとえ top.ftl で同名の変数やマクロが定義されていたとしても衝突してエラーにはなりません。

別の名前空間内の変数を編集する

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
${libA.mail}
<#assign mail="jsmith@other.com" in libA>
${libA.mail}

<#-- 出力内容
jsmith@acme.com
jsmith@other.com
-->

データモデルと名前空間

データモデルの変数はどこからでも参照できます。
例えば user という名前の変数がデータモデルにあった場合、lib/library-a.ftl はこの user 変数にアクセスできます。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) ${user}. All rights reserved.</p>
</#macro>  

<#assign mail = "${user}@acme.com">

user が"Fred"だった場合、下記のようになります。

<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 Fred. All rights reserved.</p>
Fred@acme.com
-->

ただし、名前空間内でデータモデルと同名の変数が宣言されていた場合、後者が勝ちます。

名前空間のライフサイクル

同じFTLを別名で複数回 import で読み込んだ場合、名前空間は最初の読み込みに対してのみ作られ、以後の読み込みは同一の名前空間に対する別名となります。

<#import "/lib/library-a.ftl" as libA>
<#import "/lib/library-a.ftl" as foo>
<#import "/lib/library-a.ftl" as bar>
${libA.mail}, ${foo.mail}, ${bar.mail}
<#assign mail="jsmith@other.com" in my>
${libA.mail}, ${foo.mail}, ${bar.mail}

<#-- 出力内容
jsmith@acme.com, jsmith@acme.com, jsmith@acme.com,
jsmith@other.com, jsmith@other.com, jsmith@other.com,
-->

グローバル変数とローカル変数

FTLにもグローバル変数とローカル変数があります。

assign(ローカル変数)

<#assign foo="bar">

変数の定義や置き換えができます。
特定の名前空間内に変数を作成したり、置き換えることもできます。
(<#assign someValue = "foo" in someNamespace>)

assign を利用して宣言された変数は、名前空間が変わると参照できません。

global(グローバル変数)

<#global foo="bar">

global を使って定義された変数は、すべての名前空間で参照できます。 しかし、個々の名前空間内で同名の変数が定義されていると、その名前空間からはグローバル変数の方にはアクセスできません。
ただし、その場合でも globals という特別な変数を介してアクセスすることはできます。

${.globals.foo}

globals を介してグローバル変数にアクセスできるだけでなく、データモデルの変数にもアクセスすることができます。

local(ローカル変数)

<#local foo="bar">

local 構文はマクロや関数の中でのみ使用することができます マクロ/関数内で local を利用して作られた変数は、そのマクロ/関数外からは参照できません。

つまりどうしたらいい?

  • ライブラリFTLを読み込むときは import で読み込む
  • マクロから参照したい変数が親FTLにある場合、 global を使って宣言する
  • マクロや関数内では、特に意図した場合でない限り local を使って変数宣言する

参考

FreeMarker Manual - Namespaces
FreeMarker Manual - local
FreeMarker Manual - assign
FreeMarker Manual - global