SpringMVC + Freemarker でstatic method アクセス方法 - 開発Tips

こんにちは。
FreeMarkerを使っていて、いろいろTipsがあったので、備忘録を兼ねてブログを書きます。

[構成]
freemarker-2.3.18
spring-webmvc-3.1.0.RELEASE

  • Shared variables

マニュアルをみればわかる通りですが、View(HTML、XMLJSONなど)の開発には補助的な機能・関数が必要になると思います。
http://freemarker.sourceforge.net/docs/pgui_config_sharedvariables.html

  • SpringMVC+Freemarkerで関数を開発する方法です。

[参考]
http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/view.html#views-freemarker
http://d.hatena.ne.jp/tm8r/20110915/1316097034

springのTemplateMethodModelExを実装すれば完了ですね。
あとは、FreeMarkerConfigurerを継承してcreateConfigurationをOverrideするか、直接、Bean定義のfreemarkerConfigのfreemarkerVariablesに設定するかですね。

さて、ただ以下の構成で使っていた遺産をそのまま使うと問題がありました。

[構成]
freemarker-2.3.16
spring-3.0.5
webwork-2.2.7
struts2ではありません!古いです!

  • Accessing static methods

マニュアルをみればわかる通りですが、既存のライブラリ/ユーティリティの使って、Viewの開発したい場合に使えます。
http://freemarker.sourceforge.net/docs/pgui_misc_beanwrapper.html#autoid_55

まずは、Spring+Freemarker+Webworkで関数を開発する方法です。

  • FreemarkerManagerを継承してpopulateContextメソッドをOverride
public class XxxFreemarkerManager extends FreemarkerManager {
	@Override
	public void populateContext(ScopesHashModel model, OgnlValueStack stack, Object action, HttpServletRequest request, HttpServletResponse response) {
		super.populateContext(model, stack, action, request, response);
		model.put("XxxConverter", new XxxUtil()); // おまけ:インスタンスクラスの設定方法
		try {
			// staticクラスをこのように設定すればOKです
			model.put("XxxUtil", BeansWrapper.getDefaultInstance().getStaticModels().get(XxxUtil.class.getCanonicalName()));
		} catch (TemplateModelException e) {
		}
	}
}

freemarkerのSimpleHashModelを継承した、webworkのScopesHashModelにputすれば完了ですね。
あとは、webwork.propertiesのwebwork.freemarker.manager.classnameにXxxFreemarkerManagerを指定すればDefaultConfiguratioがが読み込みます。
しかし、既存の構成だと、これができないので、UtilクラスをTemplateMethodModelExを継承してリファクタリングするしかありません。

さて、それでは、SpringMVC+Freemarkerの組み合わせで"Accessing static methods"を実現してみましょう 。

[参考]
FreeMarkerView#exposeHelpers の抜粋

	/**
	 * Expose helpers unique to each rendering operation. This is necessary so that
	 * different rendering operations can't overwrite each other's formats etc.
	 * <p>Called by <code>renderMergedTemplateModel</code>. The default implementation
	 * is empty. This method can be overridden to add custom helpers to the model.
	 * @param model The model that will be passed to the template at merge time
	 * @param request current HTTP request
	 * @throws Exception if there's a fatal error while we're adding information to the context
	 * @see #renderMergedTemplateModel
	 */
	protected void exposeHelpers(Map<String, Object> model, HttpServletRequest request) throws Exception {
	}
  • FreeMarkerViewを継承してexposeHelpersをOverride
public class XxxFreeMarkerView extends FreeMarkerView {
	@Override
	protected void exposeHelpers(Map<String, Object> model, HttpServletRequest request) throws Exception {
		super.exposeHelpers(model, request);
		// staticクラスをこのように設定すればOKです
		model.put("XxxUtil" BeansWrapper.getDefaultInstance().getStaticModels().get(XxxUtil.class.getCanonicalName()));
	}
}
  • FreeMarkerViewResolverを継承してrequiredViewClassをOverride
public class XxxFreeMarkerViewResolver extends FreeMarkerViewResolver {
	public XxxFreeMarkerViewResolver() {
		setViewClass(requiredViewClass());
	}
	@SuppressWarnings("rawtypes")
	@Override
	protected Class requiredViewClass() {
		return XxxFreeMarkerView.class;
	}
}

ということでmodelにputすればOKです。
あとは、springのbean定義で、freemarkerConfigとviewResolverを定義してあげればおしまいです。

	<bean id="freemarkerConfig"
		class="XxxFreeMarkerConfigurer">
		<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
		<property name="freemarkerSettings">
			<props>
				<prop key="default_encoding">UTF-8</prop>
			</props>
		</property>
	</bean>
	<bean id="viewResolver"
		class="XxxFreeMarkerViewResolver">
		<property name="contentType" value="text/html; charset=UTF-8" />
		<property name="cache" value="false" />
		<property name="prefix" value="" />
		<property name="suffix" value=".ftl" />
		<!-- use macro -->
		<property name="exposeSpringMacroHelpers" value="true" />
	</bean>
  • 最後に。

これで、Utilクラスのリファクタリング(TemplateMethodModelExを継承)しなくてもOKとなりました。
既存のFTLでstaticメソッドを呼び出している記述もそのまま利用できます。
めでたし。