diff --git a/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/pom.xml b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/pom.xml
new file mode 100644
index 00000000..1cbc2279
--- /dev/null
+++ b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/pom.xml
@@ -0,0 +1,43 @@
+
+
+ nutzboot-demo-simple
+ org.nutz
+ 2.2-SNAPSHOT
+
+ 4.0.0
+
+ nutzboot-demo-simple-freemarker
+ jar
+
+ nutzboot-demo-simple-freemarker
+ http://maven.apache.org
+
+
+ UTF-8
+
+
+
+
+ org.nutz
+ nutzboot-starter-nutz-mvc
+
+
+ org.nutz
+ nutzboot-starter-jetty
+
+
+ org.nutz
+ nutzboot-starter-freemarker
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ nz.net.ultraq.thymeleaf
+ thymeleaf-layout-dialect
+ 2.2.2
+
+
+
diff --git a/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/java/io/nutz/demo/simple/MainLauncher.java b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/java/io/nutz/demo/simple/MainLauncher.java
new file mode 100644
index 00000000..726b7d29
--- /dev/null
+++ b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/java/io/nutz/demo/simple/MainLauncher.java
@@ -0,0 +1,22 @@
+package io.nutz.demo.simple;
+
+import org.nutz.boot.NbApp;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.util.NutMap;
+import org.nutz.mvc.annotation.At;
+import org.nutz.mvc.annotation.Ok;
+
+@IocBean
+public class MainLauncher {
+
+ @At
+ @Ok("fm:/index")
+ public Object index(){
+ return NutMap.NEW().setv("name","wendal").setv("age",18);
+ }
+
+
+ public static void main(String[] args) {
+ new NbApp().setPrintProcDoc(true).start();
+ }
+}
diff --git a/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/application.properties b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/application.properties
new file mode 100644
index 00000000..7de1524d
--- /dev/null
+++ b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+server.port=8080
+server.host=0.0.0.0
+
+freemarker.suffix=.html
\ No newline at end of file
diff --git a/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/log4j.properties b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/log4j.properties
new file mode 100644
index 00000000..f6d90e75
--- /dev/null
+++ b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/log4j.properties
@@ -0,0 +1,8 @@
+log4j.rootLogger=info,Console
+
+log4j.logger.org.nutz.mvc=debug
+log4j.logger.org.eclipse.jetty=info
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.layout=org.apache.log4j.PatternLayout
+log4j.appender.Console.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] %5p [%t] --- %c{1}: %m%n
diff --git a/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/template/index.html b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/template/index.html
new file mode 100644
index 00000000..926e7096
--- /dev/null
+++ b/nutzboot-demo/nutzboot-demo-simple/nutzboot-demo-simple-freemarker/src/main/resources/template/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+NB demo for Freemarker
+
+
+
+
Context Path = ${obj.base!}
+
+
+
From Action name= ${obj.name}, age=${obj.age!}
+
+
+
+
diff --git a/nutzboot-demo/nutzboot-demo-simple/pom.xml b/nutzboot-demo/nutzboot-demo-simple/pom.xml
index 89a32c44..ca33c22c 100644
--- a/nutzboot-demo/nutzboot-demo-simple/pom.xml
+++ b/nutzboot-demo/nutzboot-demo-simple/pom.xml
@@ -42,7 +42,8 @@
nutzboot-demo-simple-quartz
nutzboot-demo-simple-j2cache
nutzboot-demo-simple-dao-with-slave
-
+ nutzboot-demo-simple-freemarker
+
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/pom.xml b/nutzboot-starter/nutzboot-starter-freemarker/pom.xml
new file mode 100644
index 00000000..29f70357
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/pom.xml
@@ -0,0 +1,63 @@
+
+ 4.0.0
+
+ nutzboot-starter
+ org.nutz
+ 2.2-SNAPSHOT
+
+ nutzboot-starter-freemarker
+ jar
+ nutzboot-starter-freemarker
+
+
+ UTF-8
+
+ NutzBoot, micoservice base on Nutz
+
+
+
+ Github Issue
+ http://github.com/nutzam/nutzboot/issues
+
+
+
+ The Apache Software License, Version 2.0
+ http://apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ 蛋蛋
+ 王庆华
+ TopCoderMyDream@gmail.com
+ https://github.com/TopCoderMyDream
+
+
+
+ scm:git:git://github.com/nutzam/nutzboot.git
+ scm:git:git://github.com/nutzam/nutzboot.git
+ git://github.com/nutzam/nutzboot.git
+
+
+
+ nutzcn-snapshots
+ NutzCN snapshot repository
+ https://jfrog.nutz.cn/artifactory/snapshots
+
+
+
+ sonatype-release-staging
+ Sonatype Nexus release repository
+ https://oss.sonatype.org/service/local/staging/deploy/maven2
+
+
+
+
+
+
+ org.freemarker
+ freemarker
+
+
+
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreeMarkerConfigurer.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreeMarkerConfigurer.java
new file mode 100644
index 00000000..281fc1f9
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreeMarkerConfigurer.java
@@ -0,0 +1,158 @@
+package org.nutz.boot.starter.freemarker;
+
+import freemarker.template.*;
+import org.nutz.boot.annotation.PropDoc;
+import org.nutz.ioc.Ioc;
+import org.nutz.ioc.IocContext;
+import org.nutz.ioc.Iocs;
+import org.nutz.ioc.impl.PropertiesProxy;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.lang.Files;
+import org.nutz.lang.Lang;
+import org.nutz.lang.Streams;
+import org.nutz.lang.Strings;
+import org.nutz.lang.util.ClassTools;
+import org.nutz.log.Log;
+import org.nutz.log.Logs;
+import org.nutz.mvc.Mvcs;
+
+import javax.servlet.ServletContext;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+import java.util.Map.Entry;
+
+@IocBean(create = "init")
+public class FreeMarkerConfigurer {
+
+ private final static Log log = Logs.get();
+
+ protected static final String PRE = "freemarker.";
+ public static final String PRE_SUFFIX = PRE + "suffix";
+
+ private Configuration configuration;
+ private String prefix;
+ private String suffix;
+ private FreemarkerDirectiveFactory freemarkerDirectiveFactory;
+ private Map tags = new HashMap();
+
+ public FreeMarkerConfigurer() {
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_26);
+ Ioc ioc = Mvcs.ctx().getDefaultIoc();
+ PropertiesProxy conf = ioc.get(PropertiesProxy.class,"conf");
+ this.initp(configuration, Mvcs.getServletContext(), "template", conf.get(PRE_SUFFIX,".html"), new FreemarkerDirectiveFactory());
+ }
+
+
+ protected void initp(Configuration configuration, ServletContext sc, String prefix, String suffix, FreemarkerDirectiveFactory freemarkerDirectiveFactory) {
+ this.configuration = configuration;
+ URL url = ClassTools.getClassLoader().getResource(prefix);
+ String path = url.getPath();
+ this.prefix =path;
+ this.suffix = suffix;
+ this.freemarkerDirectiveFactory = freemarkerDirectiveFactory;
+ if (this.prefix == null)
+ this.prefix = sc.getRealPath("/") + prefix;
+
+ this.configuration.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
+ this.configuration.setTemplateUpdateDelayMilliseconds(-1000);
+ this.configuration.setDefaultEncoding("UTF-8");
+ this.configuration.setURLEscapingCharset("UTF-8");
+ this.configuration.setLocale(Locale.CHINA);
+ this.configuration.setBooleanFormat("true,false");
+ this.configuration.setDateTimeFormat("yyyy-MM-dd HH:mm:ss");
+ this.configuration.setDateFormat("yyyy-MM-dd");
+ this.configuration.setTimeFormat("HH:mm:ss");
+ this.configuration.setNumberFormat("0.######");
+ this.configuration.setWhitespaceStripping(true);
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ public void init() {
+ try {
+ initFreeMarkerConfigurer();
+ Iterator> iterator = tags.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry entry = iterator.next();
+ configuration.setSharedVariable(entry.getKey(), entry.getValue());
+ }
+ if (freemarkerDirectiveFactory == null)
+ return;
+ for (FreemarkerDirective freemarkerDirective : freemarkerDirectiveFactory.getList()) {
+ configuration.setSharedVariable(freemarkerDirective.getName(), freemarkerDirective.getTemplateDirectiveModel());
+ }
+ } catch (IOException e) {
+ log.error(e);
+ } catch (TemplateException e) {
+ log.error(e);
+ }
+ }
+
+ public String getSuffix() {
+ return Strings.isBlank(freemarkerDirectiveFactory.getSuffix()) ? this.suffix : freemarkerDirectiveFactory.getSuffix();
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ protected void initFreeMarkerConfigurer() throws IOException, TemplateException {
+ String path = freemarkerDirectiveFactory.getFreemarker();
+ File file = Files.findFile(path);
+ if (!Lang.isEmpty(file)) {
+ Properties p = new Properties();
+ p.load(Streams.fileIn(file));
+ configuration.setSettings(p);
+ }
+ File f = Files.findFile(prefix);
+ configuration.setDirectoryForTemplateLoading(f);
+ }
+
+ public void setTags(Map map) {
+ Iterator> iterator = map.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry entry = iterator.next();
+ String key = entry.getKey();
+ Object obj = entry.getValue();
+ tags.put(key, obj);
+ }
+ }
+
+ public FreeMarkerConfigurer setSuffix(String suffix) {
+ this.suffix = suffix;
+ return this;
+ }
+
+ public FreeMarkerConfigurer setPrefix(String prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+
+ /**
+ *
+ * @param map
+ * @return
+ *
+ * mapTags : { factory : "$freeMarkerConfigurer#addTags", args : [ {
+ * 'abc' : 1, 'def' : 2 } ] }
+ */
+ public FreeMarkerConfigurer addTags(Map map) {
+ if (map != null) {
+ try {
+ configuration.setAllSharedVariables(new SimpleHash(map, new DefaultObjectWrapper(Configuration.VERSION_2_3_26)));
+ } catch (TemplateModelException e) {
+ log.error(e);
+ }
+ }
+ return this;
+ }
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirective.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirective.java
new file mode 100644
index 00000000..b2a772c7
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirective.java
@@ -0,0 +1,25 @@
+package org.nutz.boot.starter.freemarker;
+
+import freemarker.template.TemplateDirectiveModel;
+
+/**
+ * @author 科技㊣²º¹³
+ * 2014年1月1日 下午5:42:54
+ * http://www.rekoe.com
+ * QQ:5382211
+ */
+public class FreemarkerDirective {
+ private String name;
+ private TemplateDirectiveModel templateDirectiveModel;
+ public FreemarkerDirective(String name, TemplateDirectiveModel templateDirectiveModel) {
+ super();
+ this.name = name;
+ this.templateDirectiveModel = templateDirectiveModel;
+ }
+ public String getName() {
+ return name;
+ }
+ public TemplateDirectiveModel getTemplateDirectiveModel() {
+ return templateDirectiveModel;
+ }
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirectiveFactory.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirectiveFactory.java
new file mode 100644
index 00000000..925f5b40
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerDirectiveFactory.java
@@ -0,0 +1,56 @@
+package org.nutz.boot.starter.freemarker;
+
+import org.nutz.lang.Lang;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FreemarkerDirectiveFactory {
+
+ private List list = new ArrayList();
+
+ private String freemarker;
+
+ private String suffix;
+
+ private FreemarkerDirective[] objs;
+
+ public FreemarkerDirectiveFactory() {
+ this.freemarker = "freemarker.properties";
+ }
+
+ public FreemarkerDirectiveFactory(FreemarkerDirective... objs) {
+ this.objs = objs;
+ }
+
+ public List getList() {
+ return list;
+ }
+
+ public String getFreemarker() {
+ return freemarker;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public void init() {
+ if (Lang.isEmptyArray(objs)) {
+ return;
+ }
+ for (FreemarkerDirective freemarkerDirective : objs) {
+ list.add(freemarkerDirective);
+ }
+ }
+
+ public FreemarkerDirectiveFactory create(FreemarkerDirective... objs) {
+ if (Lang.isEmptyArray(objs)) {
+ return this;
+ }
+ for (FreemarkerDirective freemarkerDirective : objs) {
+ list.add(freemarkerDirective);
+ }
+ return this;
+ }
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerView.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerView.java
new file mode 100644
index 00000000..282f5035
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerView.java
@@ -0,0 +1,154 @@
+package org.nutz.boot.starter.freemarker;
+
+import freemarker.ext.jsp.TaglibFactory;
+import freemarker.ext.servlet.HttpRequestHashModel;
+import freemarker.ext.servlet.HttpRequestParametersHashModel;
+import freemarker.ext.servlet.HttpSessionHashModel;
+import freemarker.ext.servlet.ServletContextHashModel;
+import freemarker.template.Configuration;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateModel;
+import org.nutz.lang.Files;
+import org.nutz.lang.Lang;
+import org.nutz.lang.Strings;
+import org.nutz.mvc.Mvcs;
+import org.nutz.mvc.view.AbstractPathView;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FreemarkerView extends AbstractPathView {
+
+ private FreeMarkerConfigurer freeMarkerConfigurer;
+ private static final String ATTR_APPLICATION_MODEL = ".freemarker.Application";
+ private static final String ATTR_JSP_TAGLIBS_MODEL = ".freemarker.JspTaglibs";
+ private static final String ATTR_REQUEST_MODEL = ".freemarker.Request";
+ private static final String ATTR_REQUEST_PARAMETERS_MODEL = ".freemarker.RequestParameters";
+ private static final String KEY_APPLICATION = "Application";
+ private static final String KEY_REQUEST_MODEL = "Request";
+ private static final String KEY_SESSION_MODEL = "Session";
+ private static final String KEY_REQUEST_PARAMETER_MODEL = "Parameters";
+ private static final String KEY_EXCEPTION = "exception";
+ private static final String OBJ = "obj";
+ private static final String REQUEST = "request";
+ private static final String RESPONSE = "response";
+ private static final String SESSION = "session";
+ private static final String APPLICATION = "application";
+ private static final String KEY_JSP_TAGLIBS = "JspTaglibs";
+ public static final String PATH_BASE = "base";
+
+ public FreemarkerView(FreeMarkerConfigurer freeMarkerConfigurer, String path) {
+ super(path);
+ this.freeMarkerConfigurer = freeMarkerConfigurer;
+ }
+
+ public void render(HttpServletRequest request, HttpServletResponse response, Object value) throws Throwable {
+ String $temp = evalPath(request, value);
+ String path = getPath($temp);
+ ServletContext sc = request.getSession().getServletContext();
+ Configuration cfg = freeMarkerConfigurer.getConfiguration();
+ Map root = new HashMap();
+ root.put(OBJ, value);
+ root.put(REQUEST, request);
+ root.put(RESPONSE, response);
+ HttpSession session = request.getSession();
+ root.put(SESSION, session);
+ root.put(APPLICATION, sc);
+ root.put("props", System.getProperties());// .get("java.version")
+ Map msgs = Mvcs.getMessages(request);
+ root.put("mvcs", msgs);
+ Enumeration> reqs = request.getAttributeNames();
+ while (reqs.hasMoreElements()) {
+ String strKey = (String) reqs.nextElement();
+ root.put(strKey, request.getAttribute(strKey));
+ }
+ jspTaglibs(sc, request, response, root, cfg.getObjectWrapper());
+ try {
+ Template template = cfg.getTemplate(path);
+ response.setContentType("text/html; charset=" + template.getEncoding());
+ template.process(root, response.getWriter());
+ } catch (Exception e) {
+ throw Lang.wrapThrow(e);
+ }
+ }
+
+ /**
+ * 子类可以覆盖这个方法,给出自己特殊的后缀
+ *
+ * @return 后缀
+ */
+ protected String getExt() {
+ return freeMarkerConfigurer.getSuffix();
+ }
+
+ private String getPath(String path) {
+ StringBuffer sb = new StringBuffer();
+ // 空路径,采用默认规则
+ if (Strings.isBlank(path)) {
+ sb.append(Mvcs.getServletContext().getRealPath(freeMarkerConfigurer.getPrefix()));
+ sb.append((path.startsWith("/") ? "" : "/"));
+ sb.append(Files.renameSuffix(path, getExt()));
+ }
+ // 绝对路径 : 以 '/' 开头的路径不增加 '/WEB-INF'
+ else if (path.charAt(0) == '/') {
+ String ext = getExt();
+ sb.append(path);
+ if (!path.toLowerCase().endsWith(ext))
+ sb.append(ext);
+ }
+ // 包名形式的路径
+ else {
+ sb.append(path.replace('.', '/'));
+ sb.append(getExt());
+ }
+ return sb.toString();
+ }
+
+ protected void jspTaglibs(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, Map model, ObjectWrapper wrapper) {
+ synchronized (servletContext) {
+ ServletContextHashModel servletContextModel = (ServletContextHashModel) servletContext.getAttribute(ATTR_APPLICATION_MODEL);
+ if (Lang.isEmpty(servletContextModel)) {
+ GenericServlet servlet = JspSupportServlet.jspSupportServlet;
+ if (!Lang.isEmpty(servlet)) {
+ servletContextModel = new ServletContextHashModel(servlet, wrapper);
+ servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel);
+ TaglibFactory taglibs = new TaglibFactory(servletContext);
+ servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
+ }
+ }
+ model.put(KEY_APPLICATION, servletContextModel);
+ TemplateModel tempModel = (TemplateModel) servletContext.getAttribute(ATTR_JSP_TAGLIBS_MODEL);
+ model.put(KEY_JSP_TAGLIBS, tempModel);
+ }
+ HttpSession session = request.getSession(false);
+ if (!Lang.isEmpty(session)) {
+ model.put(KEY_SESSION_MODEL, new HttpSessionHashModel(session, wrapper));
+ }
+ HttpRequestHashModel requestModel = (HttpRequestHashModel) request.getAttribute(ATTR_REQUEST_MODEL);
+ if (Lang.isEmpty(requestModel) || !Lang.equals(requestModel.getRequest(), request)) {
+ requestModel = new HttpRequestHashModel(request, response, wrapper);
+ request.setAttribute(ATTR_REQUEST_MODEL, requestModel);
+ }
+ model.put(KEY_REQUEST_MODEL, requestModel);
+ HttpRequestParametersHashModel reqParametersModel = (HttpRequestParametersHashModel) request.getAttribute(ATTR_REQUEST_PARAMETERS_MODEL);
+ if (Lang.isEmpty(reqParametersModel) || !Lang.equals(requestModel.getRequest(), request)) {
+ reqParametersModel = new HttpRequestParametersHashModel(request);
+ request.setAttribute(ATTR_REQUEST_PARAMETERS_MODEL, reqParametersModel);
+ }
+ model.put(KEY_REQUEST_PARAMETER_MODEL, reqParametersModel);
+ Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");
+ if (Lang.isEmpty(exception)) {
+ exception = (Throwable) request.getAttribute("javax.servlet.error.JspException");
+ }
+ if (!Lang.isEmpty(exception)) {
+ model.put(KEY_EXCEPTION, exception);
+ }
+ }
+}
\ No newline at end of file
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerViewMaker.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerViewMaker.java
new file mode 100644
index 00000000..0c10e4d2
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/FreemarkerViewMaker.java
@@ -0,0 +1,47 @@
+package org.nutz.boot.starter.freemarker;
+
+import org.nutz.boot.annotation.PropDoc;
+import org.nutz.ioc.Ioc;
+import org.nutz.ioc.impl.PropertiesProxy;
+import org.nutz.ioc.loader.annotation.Inject;
+import org.nutz.ioc.loader.annotation.IocBean;
+import org.nutz.mvc.View;
+import org.nutz.mvc.ViewMaker;
+
+@IocBean(name="$views_freekmarker")
+public class FreemarkerViewMaker implements ViewMaker {
+
+ protected FreeMarkerConfigurer freeMarkerConfigurer;
+
+ protected String iocName = "freeMarkerConfigurer";
+
+ protected static final String PRE = "freemarker.";
+
+ @PropDoc(group = "freemarker", value = "文件后缀",defaultValue = ".html")
+ public static final String PROP_SUFFIX = PRE + "suffix";
+
+
+ @Inject
+ PropertiesProxy conf;
+
+
+ public View make(Ioc ioc, String type, String value) {
+ if ("fm".equalsIgnoreCase(type) || "ftl".equalsIgnoreCase(type)) {
+ if (freeMarkerConfigurer == null) {
+ for (String name : ioc.getNames()) {
+ if (iocName.equals(name)) {
+ freeMarkerConfigurer = ioc.get(FreeMarkerConfigurer.class);
+ break;
+ }
+ }
+ if (freeMarkerConfigurer == null) {
+ freeMarkerConfigurer = new FreeMarkerConfigurer();
+ freeMarkerConfigurer.init();
+ }
+ }
+ return new FreemarkerView(freeMarkerConfigurer, value);
+ }
+ return null;
+ }
+
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/JspSupportServlet.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/JspSupportServlet.java
new file mode 100644
index 00000000..7e805598
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/JspSupportServlet.java
@@ -0,0 +1,17 @@
+package org.nutz.boot.starter.freemarker;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+/**
+ */
+public class JspSupportServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 8302309812391541933L;
+ public static JspSupportServlet jspSupportServlet;
+ public void init(ServletConfig servletConfig) throws ServletException {
+ super.init(servletConfig);
+ jspSupportServlet = this;
+ }
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/directive/CurrentTimeDirective.java b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/directive/CurrentTimeDirective.java
new file mode 100644
index 00000000..aa9b22b8
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/java/org/nutz/boot/starter/freemarker/directive/CurrentTimeDirective.java
@@ -0,0 +1,25 @@
+package org.nutz.boot.starter.freemarker.directive;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import org.nutz.lang.Times;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+/**
+ * 执行时间标签
+ *
+ */
+public class CurrentTimeDirective implements TemplateDirectiveModel {
+
+ @SuppressWarnings({ "rawtypes" })
+ public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
+ Writer out = env.getOut();
+ out.append(Times.format("yyyy-MM-dd HH:mm", Times.now()));
+ }
+}
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/META-INF/nutz/org.nutz.boot.starter.NbStarter b/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/META-INF/nutz/org.nutz.boot.starter.NbStarter
new file mode 100644
index 00000000..86936780
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/META-INF/nutz/org.nutz.boot.starter.NbStarter
@@ -0,0 +1 @@
+org.nutz.boot.starter.freemarker.FreemarkerViewMaker
\ No newline at end of file
diff --git a/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/freemarker.properties b/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/freemarker.properties
new file mode 100644
index 00000000..2744c1bc
--- /dev/null
+++ b/nutzboot-starter/nutzboot-starter-freemarker/src/main/resources/freemarker.properties
@@ -0,0 +1,9 @@
+#demo configure
+url_escaping_charset=UTF-8
+locale=zh_CN
+boolean_format=true,false
+datetime_format=yyyy-MM-dd HH:mm:ss
+date_format=yyyy-MM-dd
+time_format=HH:mm:ss
+number_format=0.######
+#auto_import=/ftl/pony/index.ftl as p,/ftl/spring.ftl as s
diff --git a/nutzboot-starter/pom.xml b/nutzboot-starter/pom.xml
index 5503b98d..527d7565 100644
--- a/nutzboot-starter/pom.xml
+++ b/nutzboot-starter/pom.xml
@@ -55,6 +55,7 @@
nutzboot-starter-apollo-client
nutzboot-starter-config-client
nutzboot-starter-j2cache
+ nutzboot-starter-freemarker
diff --git a/pom.xml b/pom.xml
index eb7e518e..7bb4fd5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -793,6 +793,16 @@
nutzboot-starter-j2cache
${nutzboot.version}
+
+ org.freemarker
+ freemarker
+ 2.3.26-incubating
+
+
+ org.nutz
+ nutzboot-starter-freemarker
+ ${nutzboot.version}
+