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类
@Controller
@RequestMapping(value="/web")
public class IndexController extends AbstractController {
//行业资讯接口
@Resource
private INewInfoService newInfoService;
@RequestMapping(value="/index")
public ResponseEntity index(){
try {
Map data = new HashMap();
//行业资讯
data.put("infomations", getInfomation());
return super.getFreeMark("/res/web/index.html", data);
} catch (IOException e) {
return new ResponseEntity(e.getMessage(),HttpStatus.NOT_FOUND);
}
}
}
controller类IndexController继承AbstractController,使用它的getFreeMark(String url, Map data)方法
(2)AbstractController类
public abstract class AbstractController {
private FreeMarker freeMarker = null;
public AbstractController() {
super();
}
protected ResponseEntity getFreeMark(String name,Map 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 buildData(Map data) {
if(data==null){
data = new HashMap();
}
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();
}
}
(3)FreeMarker类
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 getFreeMark(String name,Map 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(text,responseHeaders,HttpStatus.OK);
} catch (TemplateException e) {
throw new IOException(e);
}
}
public Configuration getConfiguration(){
return cfg;
}
}
(4)资源模板加载器ResourceTemplateLoader
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;
}
}
我们这里继承freemarker.cache.URLTemplateLoader,但要重写URL getURL(String name)方法,另外也实现了原来freemarker.cache.TemplateLoader接口的方法,
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);