在採用FreeMarker做前臺視圖模板的情況下,我們可以通過<#include>標籤和自定義宏來解決很多重複性工作。
一個簡單的FreeMarker宏:
<#macro sayHello name="">
hello ${name}
</#macro>
然後通過如下的形式調用:
<@sayHello name="shannon" />
不過這種在模板頁中定義的宏能力有限。【1】假設,我們很多頁面都要輸出一個熱門排行框,而排行數據需要從controller層動態獲取,我們可以用這種宏來完成所有的展示工作,但前提是相應的controller和接口中層需要預先將這些排行數據放到model中去,因此對於後端來說這也是一個重複性的工作。那麼有沒有一種方式可以讓後端也脫離這種重複工作呢?答案是肯定的,這也是寫這篇博客的目的。
在一個偶然的機會發現jeecms項目中用到了這種方式,於是借鑑了一番。
FreeMarker不僅可以在前端的模板頁中定義宏,還可以通過擴展其接口在後端實現宏,這有什麼好處呢?這種方式就好比讓你的模板頁具備了從前端再次回到後端的能力。這樣我們就能很好的解決【1】處的假設,我們無需在各個controller的各個接口中去重複的向model中添加所需的排行數據,而是當FreeMarker渲染模板頁時遇到相應的宏它可以回到後端去調用相應的方法取到所需的數據。例子如下:
import freemarker.core.Environment;
import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateDirectiveModel;
/**
* FreeMarker自定義宏
* 獲取App下載排行列表
* 參數包括 length(列表長度) mtypeCode(主類型代碼) typeCode(小類型代碼) rankMode(排行模式1、2、3)
* @author shannon
*
*/
public class FMAppRankDirective implements TemplateDirectiveModel {
@Resource(name = "appRankService")
private AppRankService appRankService;
@SuppressWarnings("unchecked")
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
//DirectiveUtils是借用jeecms項目中的工具類,主要是因爲它集成了一些異常處理功能,
//其實完全可以不用它,params是個Map,自己通過key取值就可以了,做一下空值判斷
Integer length = DirectiveUtils.getInt("length", params);
Integer mtypeCode = DirectiveUtils.getInt("mtypeCode", params);
Integer typeCode = DirectiveUtils.getInt("typeCode", params);
Integer rankMode = DirectiveUtils.getInt("rankMode", params);
ArrayList<App> rankList = appRankService.getRankList(length, mtypeCode, typeCode, rankMode);
env.setVariable("appRankList", ObjectWrapper.DEFAULT_WRAPPER.wrap(rankList));
if (body != null) {
body.render(env.getOut());
}
}
}
通過實現FreeMarker的TemplateDirectiveModel就在後端實現了一個自定義的宏,這個宏的功能很簡單,只是根據給定的參數將排行數據“appRankList”放到model中去,然後模板頁中就可以使用這個變量了。
FreeMarker的配置參數中需要將這個宏加入進去。
<bean id="appRankDirective" class="com.shannon.example.rank.util.FMAppRankDirective" />
<bean id="freemarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
……其他配置略……
<property name="freemarkerVariables">
<map>
……其他配置略……
<entry key="appRankDirective" value-ref="appRankDirective"/>
</map>
</property>
</bean>
在模板頁中使用:
<#-- 應用下載排行框,title爲該框的標題,length爲排行列表長度,mtypeCode爲主類型代碼,typeCode爲小類型代碼,rankMode爲排行方式
1爲總下載量,2爲月下載量,3爲昨日增長下載量
-->
<#macro appRankBox title="" length=10 mtypeCode=1 typeCode=-1 rankMode=1>
<@appRankDirective length=length mtypeCode=mtypeCode typeCode=typeCode rankMode=rankMode />
<h3 class="box-title">${title}</h3>
<div class="box">
<ul class="row-list">
<#list appRankList as item>
……詳細輸出內容略……
</#list>
</ul>
</div>
</#macro>
這裏我在模板頁中又定義了一個宏,負責內容及樣式的輸出,因爲模板頁中的宏比較直觀,讓後端的宏只負責拿數據。其他頁面直接使用“appRankBox”就可以了,然後由它來調用後端的“appRankDirective”宏來拿數據。
這樣,controller就從重複工作中脫身了。