1、一些概念
(1)模板加载器:模板加载器是加载基于抽象模板路径下,比如"index.ftl"或"products/catalog.ftl"的原生文本数据对象。这由具体的模板加载器对象来确定它们取得请求数据时使用了什么样的数据来源(文件夹中的文件,数据等等)。当调用cfg.getTemplate(这里的cfg就是Configuration实例)时,FreeMarker询问模板加载器是否已经为cfg建立返回给定模板路径的文本,之后FreeMarker解析文本生成模板。
(2)内建模板加载器
在Configuration中可以使用下面的方法来方便建立三种模板加载。(每种方法都会在其内部新建一个模板加载器对象,然后创建Configuration实例来使用它。)
void setDirectoryForTemplateLoading(File dir);
void setClassForTemplateLoading(Class cl, String prefix);
(3)从多个位置加载模板
如果需要从多个位置加载模板,那就不得不为每个位置都实例化模板加载器对象,将它们包装到一个被称为MultiTemplateLoader的特殊模板加载器,最终将这个加载器传递给Configuration对象的setTemplateLoader(TemplateLoader loader)方法。下面给出一个使用类加载器从两个不同位置加载模板的示例:
import freemarker.cache.*; // 模板加载器在这个包下
...
FileTemplateLoader ftl1 = new FileTemplateLoader(new File("/tmp/templates"));
FileTemplateLoader ftl2 = new FileTemplateLoader(new File("/usr/data/templates"));
ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "");
TemplateLoader[] loaders = new TemplateLoader[] { ftl1, ftl2, ctl };
MultiTemplateLoader mtl = new MultiTemplateLoader(loaders);
cfg.setTemplateLoader(mtl);
(4)从其他资源加载模板
如果内建的类加载器都不适合使用,那么就需要来编写自己的类加载器了,这个类需要实现freemarker.cache.TemplateLoader接口,然后将它传递给Configuration对象的setTemplateLoader(TemplateLoader loader)方法。
如果你的模板需要通过URL访问其他模板,那么就不需要实现TemplateLoader接口了,可以选择子接口freemarker.cache.URLTemplateLoader来替代,只需实现URL getURL(String templateName)方法即可。
2、自定义freemarker的模板加载
从(4)从其他资源加载模板,我们已经知道自定义模板加载器的思路了。我们这里讨论的是模板需要通过URL访问其他模板,因此,我们会继承freemarker.cache.URLTemplateLoader类,并实现URL getURL(String templateName)方法。当然,我们也会实现所有TemplateLoader接口的方法。
(1)controller类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@Controller
@RequestMapping
(value=
"/web"
)
public
class
IndexController
extends
AbstractController {
//行业资讯接口
@Resource
private
INewInfoService newInfoService;
@RequestMapping
(value=
"/index"
)
public
ResponseEntity<string> index(){
try
{
Map<string,object> data =
new
HashMap<string,object>();
//行业资讯
data.put(
"infomations"
, getInfomation());
return
super
.getFreeMark(
"/res/web/index.html"
, data);
}
catch
(IOException e) {
return
new
ResponseEntity<string>(e.getMessage(),HttpStatus.NOT_FOUND);
}
}
}</string></string,object></string,object></string>
|
(2)AbstractController类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public
abstract
class
AbstractController {
private
FreeMarker freeMarker =
null
;
public
AbstractController() {
super
();
}
protected
ResponseEntity<string> getFreeMark(String name,Map<string,object> data)
throws
IOException{
try
{
data = buildData(data);
if
(freeMarker==
null
){
freeMarker =
new
FreeMarker(
this
.getRequest(),
this
.getClass());
}
freeMarker.setSessionId(
this
.getSession().getId());
return
freeMarker.getFreeMark(name, data);
}
catch
(TemplateException e) {
throw
new
IOException(e);
}
}
private
Map<string, object=
""
> buildData(Map<string, object=
""
> data) {
if
(data==
null
){
data =
new
HashMap<string,object>();
}
HttpServletRequest request =
this
.getRequest();
Enumeration<!--?--> e = request.getAttributeNames();
while
(e.hasMoreElements()){
String element = e.nextElement().toString();
data.put(element, request.getAttribute(element));
}
return
data;
}
protected
HttpServletRequest getRequest(){
return
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
}
protected
HttpSession getSession(){
return
getRequest().getSession();
}
protected
ServletContext getServletContext(){
return
getRequest().getSession().getServletContext();
}
}</string,object></string,></string,></string,object></string>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
public
class
FreeMarker {
Configuration cfg;
ResourceTemplateLoader loader;
public
FreeMarker(HttpServletRequest request,Class<!--?--> resJAR)
throws
IOException, TemplateException{
FreeMarkerConfigurer configurer =
new
FreeMarkerConfigurer();
Properties settings = buildSettings();
configurer.setFreemarkerSettings(settings);
InputStream inputStream = loadConfigFile(configurer);
loader =
new
ResourceTemplateLoader(request,resJAR);
configurer.setPreTemplateLoaders(
new
TemplateLoader[]{loader});
cfg = configurer.createConfiguration();
cfg.setLocalizedLookup(
false
);
cfg.setTemplateUpdateDelay(-
1
);
if
(inputStream!=
null
){
inputStream.close();
}
}
private
InputStream loadConfigFile(FreeMarkerConfigurer configurer) {
InputStream inputStream =
this
.getClass().getResourceAsStream(
"/freemarker.properties"
);
if
(inputStream!=
null
){
Resource resource =
new
InputStreamResource(inputStream);
configurer.setConfigLocation(resource);
}
return
inputStream;
}
private
Properties buildSettings() {
Properties settings =
new
Properties();
settings.put(
"classic_compatible"
,
"true"
);
settings.put(
"whitespace_stripping"
,
"true"
);
settings.put(
"template_update_delay"
,
"300"
);
settings.put(
"locale"
,
"zh_CN"
);
settings.put(
"default_encoding"
,
"utf-8"
);
settings.put(
"url_escaping_charset"
,
"utf-8"
);
settings.put(
"date_format"
,
"yyyy-MM-dd"
);
settings.put(
"time_format"
,
"HH:mm:ss"
);
settings.put(
"datetime_format"
,
"yyyy-MM-dd HH:mm:ss"
);
settings.put(
"number_format"
,
"#"
);
settings.put(
"boolean_format"
,
"true,false"
);
settings.put(
"output_encoding"
,
"UTF-8"
);
settings.put(
"tag_syntax"
,
"auto_detect"
);
return
settings;
}
protected
Template getTemplate(String name)
throws
IOException{
disponseCache();
//启用调式模式
return
cfg.getTemplate(name);
}
public
void
disponseCache(){
synchronized
(cfg) {
cfg.clearTemplateCache();
}
}
public
void
setSessionId(String sessionid){
loader.setSessionId(sessionid);
}
public
ResponseEntity<string> getFreeMark(String name,Map<string,object> data)
throws
IOException{
try
{
Template template =
this
.getTemplate(name);
HttpHeaders responseHeaders =
new
HttpHeaders();
responseHeaders.set(
"Content-Type"
,
"text/html;charset=utf-8"
);
String text = FreeMarkerTemplateUtils.processTemplateIntoString(template, data);
return
new
ResponseEntity<string>(text,responseHeaders,HttpStatus.OK);
}
catch
(TemplateException e) {
throw
new
IOException(e);
}
}
public
Configuration getConfiguration(){
return
cfg;
}
}</string></string,object></string>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
class
ResourceTemplateLoader
extends
URLTemplateLoader {
private
String urlPrefix =
null
;
private
String filePrefix =
"/"
;
private
Class<!--?--> resJAR;
private
String sessionid;
private
static
String STATIC_RESOURCE_FOLDER =
"resource"
;
public
ResourceTemplateLoader(HttpServletRequest request, Class<!--?--> resJAR) {
super
();
String path = request.getContextPath();
String basePath = request.getScheme() +
"://"
+ request.getServerName()
+
":"
+ request.getServerPort() + path +
"/"
;
this
.urlPrefix = basePath;
this
.resJAR = resJAR;
this
.sessionid = request.getSession().getId();
}
@Override
protected
URL getURL(String name) {
try
{
if
(name.startsWith(
"mvc/"
)) {
return
new
URL(urlPrefix + name);
}
else
if
(name.startsWith(
"res/"
)){
String pluginFile = PluginsMananger.pluginsMap.get(getModuleName(name.substring(
4
)));
String resource = name.substring(
4
);
String moduleName = getModuleName(resource);
String resourceUrl = resource.replace(moduleName, STATIC_RESOURCE_FOLDER);
URI uri =
new
URI(
"jar:file:"
+pluginFile+
"!"
+filePrefix + resourceUrl);
return
uri.toURL();
//return new URL(urlPrefix + name);
}
else
{
return
resJAR.getResource(filePrefix + name);
}
}
catch
(MalformedURLException e) {
e.printStackTrace();
return
null
;
}
catch
(URISyntaxException e) {
e.printStackTrace();
return
null
;
}
}
private
String getModuleName(String pathInfo) {
String moduleName =
null
;
// 过滤掉第一个/
if
(
0
== pathInfo.indexOf(
"/"
)) {
pathInfo = pathInfo.substring(pathInfo.indexOf(
"/"
) +
1
);
}
// 获取模块名称,规则:
// 1、获取第一个/前的字符
// 2、如没有/时,获取整个字符,但不能是静态资源
// pathInfo中含有/时,/前的字符串即为模块名称
if
(-
1
!= pathInfo.indexOf(
"/"
)) {
moduleName = pathInfo.substring(
0
, pathInfo.indexOf(
"/"
));
}
// pathInfo中不含有/时, 同时也没有携带资源,pathInfo即为模块名称
else
if
(-
1
== pathInfo.indexOf(
"/"
) && -
1
== pathInfo.indexOf(
"."
)) {
moduleName = pathInfo;
}
return
moduleName;
}
public
long
getLastModified(Object templateSource) {
return
((SessionURLTemplateSource) templateSource).lastModified();
}
public
Reader getReader(Object templateSource, String encoding)
throws
IOException {
return
new
InputStreamReader(
((SessionURLTemplateSource) templateSource).getInputStream(), encoding);
}
public
void
closeTemplateSource(Object templateSource)
throws
IOException {
((SessionURLTemplateSource) templateSource).close();
}
public
Object findTemplateSource(String name)
throws
IOException {
URL url = getURL(name);
return
url ==
null
?
null
:
new
SessionURLTemplateSource(url,sessionid);
}
public
void
setSessionId(String sessionid){
this
.sessionid = sessionid;
}
}
|
long getLastModified(Object templateSource),Reader getReader(Object templateSource, String encoding),closeTemplateSource(Object templateSource),findTemplateSource(String name)。其中Reader getReader(Object templateSource, String encoding)是非常关键,因为最后模板是在这里加载的。
通过源码分析,FreeMarker对Template的加载过程是这样的
Configuration(getTemplate)-->TemplateCache(getTemplate)-->TemplateCache(loadTemplate)-->TemplateLoader(getReader)
(5)SessionURLTemplateSource类
SessionURLTemplateSource类就是源码freemarker.cache.URLTemplateSource类,但我们这里对它进行一点改动,即在它的构造方法中添加String sessionid的参数,另外在构造方法中多添加一行代码this.conn.setRequestProperty( "Cookie", "JSESSIONID="+sessionid);