FreeMarkerの全template共通で使用する変数をShared variablesで設定する
Shared variables
FreeMarkerの全てのテンプレート共通で使用する変数を設定するにはShared variablesを使えばいい。
マニュアルの通り以下のように設定できる。
Configuration cfg = new Configuration(Configuration.VERSION_2_3_27);
...
cfg.setSharedVariable("warp", new WarpDirective());
cfg.setSharedVariable("company", "Foo Inc.");
Spring BootからShared variablesを設定する
Spring Bootの2系ではShared variablesをスマートに設定する方法が用意されておらず、BeanPostProcessor
を使ってBeanが作成される前後に処理を挟むという力技を使わなければいけない。
またSpring Bootのauto-configurationの仕組みを壊さないように、FreeMarkerのfreemarker.template.Configuration
クラスを直接触らないで、SpringのFreeMarkerConfigurer
クラスを通して設定変更する必要がある。
例えば環境毎に設定したapplication.propertiesの設定値template.xを変数xに@Value
で読み込んで、FreeMarkerで${x}
で使えるようにするには次のようにする。
@Component
public class FreeMarkerConfigurerPostProcessor implements BeanPostProcessor {
@Value("${template.x}")
String x;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
Map<String, Object> sharedVariables = new HashMap<>();
sharedVariables.put("x", x);
configurer.setFreemarkerVariables(sharedVariables);
}
return bean;
}
}
Shared variablesを使わずModel#addAttribute
する
Shared variablesを使わないなら、Spring Bootの全ControllerでModel#addAttribute
をしなくてはならない。
共通メソッドを作って毎回呼び出すようにすると、漏れが発生するリスクがある。漏れは例えば以下のような場合に発生する。
Accept: text/html
をHTTPヘッダーにつけた上で、CSRFトークンをセットせずにPOSTする- Spring SecurityがCSRFトークンの検証をNG判定する
- Spring Securityが
/error
にフォワードする ※Acceptヘッダーがtext/html
でなければ403レスポンスをJSONで返すが、text/html
の場合は/error
にフォワードしてHTMLファイルを返そうとする /error
に対応するFreeMarkerのテンプレートerror.ftlh
があれば、それを元にHTMLファイルにレンダリングしようとするerror.ftlh
で変数が使用されていれば、ErrorController
をimplements
したコントローラークラスに@RequestMapping("/error")
のメソッドを定義し、そこでModel#addAttribute
していない限りNullPointerExceptionが発生する
Spring Securityによるエラーページへのフォワードなど、正常系で想定している範囲外にも漏れなく対応するには、@ControllerAdvice
と@ModelAttribute
を使って全共通処理が実行されるようにする必要がある。
@ControllerAdvice
public class ModelAttributeAdvice {
@ModelAttribute
public void addAttributes(Model model, @Value("${template.x}" String x) {
model.addAttribute("x", x);
}
}
環境
- Java 11
- Spring Boot 2.2.3
- FreeMarker 2.3.29
参考
https://github.com/spring-projects/spring-boot/issues/8965#issuecomment-369845407
https://freemarker.apache.org/docs/pgui_config_sharedvariables.html