使用Freemarker宏进行可扩展式模块化编程
一、前言
今天的文章聊一下freemarker的一些特性:宏,我们将使用它写出一些模块化,可扩展的页面代码,这样的复用并且可扩展的代码风格正是我一直所追求的优雅。
二、需求案例
干巴巴的代码没意思,我们拿一个实际应用的例子。
先看一下我们具体的需求,以我的博客网站为例,比较首页 及markdown编辑器页 可以发现他们的公共点即头部导航栏。
可以发现公共点除了头部导航行,还包括博客大标题及右侧导航栏,用面向对象中的继承关系我们可以将它们表示如下:
正如同类可以通过基类定义通用功能实现复用,通过继承扩展一样,freemarker的页面是不是也可以定义基础模板,并经过类似继承的手段来实现复用和扩展呢,答案自然是有的,这个就是我们今天的所谈到的。
三、语法实现
首先是baseMarco.ftl 基本模板宏:
1.baseMarco.ftl
<#compress>//在基本宏里定义#compress 压缩页面指令, 扩展页就不需要定义了
<#macro base base_title base_keywords="" base_js=[] base_css=[]>
//base: 模板名 base_title base_keywords 可由扩展页传入的标题和关键字
//base_js css 由扩展页传入其自己的css js 我这里定义的是一个数据,方便传入多个
<html lang="zh-CN">
<head>
<title>初的博客-${base_title}</title>
//标题 后缀为扩展页所传入
//公共css
<link rel="stylesheet"
href="//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
//遍历扩展页css
<#list base_css as c>
<link rel="stylesheet" href="${staticsPath}${c}">
</#list>
<body id="main-body" >
<div class="container">
<ul class="nav nav-pills">
<li ><a href="/">首页</a></li>
<li ><a href="/articles.html">文章</a></li>
...
</ul>
</div>
//以上是公共导航栏
//#nested 指令表示扩展页内容将嵌套在此处
<#nested>
//以下是公共页脚
<footer class="blog-footer">
<p>? 2015-2016 初</p>
</footer>
</body>
//公共js
<script src="http://apps.bdimg.com/libs/jquery/1.11.3/jquery.min.js"></script>
//遍历公共js
<#list base_js as j>
<script src="${staticsPath}${j}"></script>
</#list>
</html>
</#macro>
</#compress>
ok,然后是扩展的markdown页。 有了基本宏之后,扩展页就只需要填写自己的内容了,代码非常干净。
2.markdown.ftl:
//引入基本宏
<#include "WEB-INF/views/baseMacro.ftl">
//@base 基本宏的名字 base_title 本页标题,与base中的前缀拼接就成为了该页完整标题 Chu Lung's Blog-markdown编辑器
//base_js css 本页自有的js和css
<@base base_title="markdown编辑器" base_js=["/markdown/editormd.js","/markdown/main.js"] base_css=["/markdown/css/editormd.css"] base_keywords="在线markdown编辑器,editormd">
//@base 中间的内容将嵌套至 base 宏中的#nested处
<div id="layout">
<div id="editor-div"></div>
</div>
</@base>
以上就完成了markdown页面的扩展。
freemarker的宏嵌套不仅可以嵌套内容,同样也可以嵌套宏,就如同子类继承父类,"孙"类还可以继承子类一样。
前面说到了,首页和文章全文页的公共点除了导航栏,还有大标题和侧边栏。因此我们需要扩展宏,让它包含这两页面的公共内容。
3.pageMarco.ftl
//宏名字 page
<#macro page title js=[] css=[] keywords="">
//引入base宏
<#include "WEB-INF/views/baseMacro.ftl">
//标题 js css等让下一级扩展页传入 keywords本页赋值
<@base base_title=title base_js=js base_css=css base_keywords="个人博客,java,初">
//以下内容将被嵌套至base宏中#nested指令处, 注意内容中又包含一个#nested指令
<div class="container">
//公共大标题
<div class="blog-header">
<h1 class="blog-title">初的博客</h1>
<p class="lead blog-description">唯爱、技术和美食三者不可辜负.</p>
</div>
<div class="row">
<div class="col-sm-8 blog-main">
//该指令表明下一级扩展页内容将被嵌套至此
<#nested>
</div>
//公共侧边栏
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<div class="sidebar-module sidebar-module-inset">
<h4>Hi</h4>
<p>欢迎来到我的博客</p>
</div>
<#-- <div class="sidebar-module">
<h4>标签</h4>
<ol class="list-unstyled">
<li><a href="#">March 2014</a></li>
</ol>
</div>
-->
<div class="sidebar-module">
<h4>档案</h4>
<ol id="articleFilings" class="list-unstyled">
</ol>
</div>
</div>
</div>
</div>
</@base>
</#macro>
然后首页就可以嵌套至page中了,文章页也一样,这里就不再累述了。
4.index.ftl
<#include "WEB-INF/views/pageMacro.ftl">
<@page title="首页" js=["/blog/js/common.js"]>
<div>
...
</div>
</@page>
四、结语
可能是java的面向对象接触得多了,用其他语言,包括freemarker这种标记语言我都会下意识的考虑如何似组合和继承写出优雅的代码,为此需要去查询许多资料,这样其实是会花不少时间琢磨的,可能花费数天。或许页面之间copy会来的更快,几分钟就ok。但正因为可复用和扩展的代码是需要精心设计的,这可以让我保持思考,不成为一个纯粹的码农,反而可以享受编程的乐趣,保持自信。