Fork me on GitHub

模板引擎

  • 简洁友好的模板语法
  • 精益求精的性能优化
  • 高质量的设计与实现

集成

HTTL已内置集成常用MVC框架,你也可以自行使用HTTL的API进行集成。

API集成

API一览表

Engine: (加载后不可变,线程安全,请复用单例)

  • Engine.getEngine() 获取引擎单例
  • Engine.getTemplate(name) 基于模板名查询模板实例
  • Engine.parseTemplate(src) 解析模板源码为模板实例
  • Engine.getResource(name) 获取资源文件
  • Engine.hasResource(name) 判断资源文件是否存在
  • Engine.getProperty(key) 获取配置项属性值
  • Engine.getName() 获取配置文件名
  • Engine.getVersion() 获取HTTL版本

Template: (继承于Node和Resource,每模板原型实例,加载后不可变类,线程安全,热加载将产生不同实例)

  • Template.render(map,writer) 基于参数渲染模板内容到输出中
  • Template.evaluate(map) 基于参数求值模板内容
  • Template.getVariables() 获取需要的参数类型
  • Template.getMacros() 获取模板中的宏
  • Template.isMacro() 当前模板是否为宏
  • Node.accept(Visitor) 访问语法树
  • Node.getOffset() 获取片断源位置偏移量
  • Node.getParent() 获取父节点
  • Node.getChildren() 获取子节点
  • Resource.openReader() 获取模板源读取器
  • Resource.openStream() 获取模板源输入流
  • Resource.getSource() 获取模板源代码
  • Resource.getName() 获取模板源名称
  • Resource.getEncoding() 获取模板源编码
  • Resource.getLastModified() 获取模板源最后修改时间
  • Resource.getLocale() 获取模板本地化区域
  • Resource.getLength() 获取模板源长度

Context: (继承于Map,线程绑定实例,线程栈内无竞争使用,线程安全,请不要跨线程传递)

  • Context.getContext() 获取当前线程上下文
  • Context.getParent() 获取上一级上下文
  • Context.getTemplate() 获取当前执行的模板
  • Context.getEngine() 获取当前执行的引擎
  • Context.getOut() 获取当前输出
  • Context.getLevel() 获取当前上下文层级
  • Map.get(String) 获取上下文变量
  • Map.put(String,Object) 写法上下文变量

API集成示例

import httl.*;
import java.util.*;

Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("user", user);
parameters.put("books", books);

Engine engine = Engine.getEngine();
Template template = engine.getTemplate("/books.httl");
template.render(parameters, response.getOutputStream());

注:缺省配置下,HTTL不依赖任何三方库,只需JDK1.5+即可。

注:缺省必须要用JDK运行,如果只有JRE,请配置为JavassistCompiler。

多份配置:

Engine engine = Engine.getEngine("xxx.properties"); // 不同配置产生不同Engine实例

编程配置:

Properties properties = new Properties();
properties.setProperty("loaders", "com.your.YourLoader");
Engine engine = Engine.getEngine("xxx", properties); // 不同ID产生不同Engine实例

获取属性:

// 如果只配了单 Loader,可以通过这种方式获取属性实例:
Loader loader = engine.getProperty("loaders", Loader.class); 
// 如果你配了多个Loader,可以:
Loader[] loaders = engine.getProperty("loaders", Loader[].class);
// 如果是文本,可以直接获取:
String encoding = engine.getProperty("output.encoding");

下面的调用只是演示集成中你能获取到的信息:

// 你可以传入Template对象,在模板中直接调用:$!{template}
// 注意:HTTL在发现Template对象时,会直接把output往下传,而不是拷贝结果。
parameters.put("template", template);

// 基于参数渲染到输出:
// parameters可以是Map,或者Pojo对象,或者Object[],或者JSON串。
// out可以是OutputStream或Writer。
template.render(parameters, out);

// 你也可以执行模板拿到渲染结果:
String result = (String) template.evaluate(parameters); 
// 注意:如果你只是为了把A模板的结果传给B模板,请不要用这种先求值,再传变量的方式。
// 因为这会浪费一次内存拷贝,请直接将template传入,可以减少result中间变量的内存占用。
parameters.put("template", result);  // 错误用法,应直接传入template对象

// 你可以拿到模板中的set赋值:
// 注意:如果要在模板渲染完之后拿到变量,要用#set(title :="foo")写到上级Context中。
// 因为模板渲染完时会弹出Context栈,模板Context中的变量都会消失,只有写到上级Context中变量保留。
String title = (String) Context.getContext().get("title");

// 你也可以拿到模板中的宏:(你可以把宏理解为片断模板)
Template macro = template.getMacros().get("menus");

// 如果你要写测试工具,你可以获取到模板所需的变量和类型,去Mock数据。
Map<String, Class<?>> variables = template.getVariables();

ScriptEngine集成

你也可以使用JDK标准的脚本API,这样可以不显式依赖HTTL的任何类:

import javax.script.*

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("httl");

Bindings bindings = engine.createBindings();
bindings.put("hello","world");

CompiledScript script = engine.compile("${hello}");
String result = (String) script.eval(bindings);

配置pom.xml依赖:

<project>
    <dependencies>
        <dependency>
            <groupId>com.github.httl</groupId>
            <artifactId>httl-script</artifactId>
            <version>1.0.11</version>
            </dependency>
    </dependencies>
<project>

扩展集成

扩展约定

  • 1. 通过setter注入配置项及依赖,如:setEncoding(String), setCompiler(Compiler)
  • 2. 属性用大写分隔,对应配置用点号分隔,如:setFooBar(String),配置:foo.bar=val
  • 3. 当注入类型为数组时,以逗号分隔多个值,如:setFoo(String[]),配置:foo=v1,v2
  • 4. 如果值int或boolean等基本类型,将自动转换,如:setFoo(boolean),配置:foo=true
  • 5. 如果值为空或字符串null时,注入的setter方法不会被执行,在setter内不用判断null值。
  • 6. 如果有init()初始化方法,将在属性注入完成后执行。
  • 7. 如果有inited()事件方法,将在所有扩展点初始化完后执行,以初始化先后逆序执行。
  • 8. 如果setter以@Reqiured标注,表示如果该属性没有注入,当前扩展点不加载。
  • 9. 如果setter以@Optional标注,表示如果没有任意一个Optional属性被注入,当前扩展点不加载。
  • 10. 基于同类型构造函数进行AOP包装,并用^=配置,如:public XxxWrapLoader(Loader) {},配置:loader^=XxxWrapLoader

配置注入

public MyFilter implements Filter {

	private String outputEncoding;

	// 将注入httl.properties中的output.encoding=UTF-8配置项
	public void setOutputEncoding(String outputEncoding) {
		this.outputEncoding = outputEncoding;
	}

	private Compiler compiler;

	// 将注入httl.properties中的compiler配置项
	// 并且将实例化和初始化好compiler的属性
	public void setCompiler(Compiler compiler) {
		this.compiler = compiler;
	}

	private Engine engine;

	// 将注入Engine本身
	public void setEngine(Engine engine) {
		this.engine = engine;
	}
	
	// 当属性注入完后执行
	public void init() {
	}

	public String filter(String value) {
		// ...
	}

}

支持的扩展点

# 方法扩展
import.methods=StaticMethodClass

# 模板加载器
loaders=httl.spi.Loader

# 模板语法解析器
template.parser=httl.spi.Parser

# 表达式语法解析器
expression.parser=httl.spi.Parser

# 模板编译转换器
translator=httl.spi.Translator

# JAVA编译器
compiler=httl.spi.Compiler

# 日志输出
loggers=httl.spi.Logger

# 属性决策器
resolvers=httl.spi.Resolver

# 模板缓存
template.cache=java.util.Map

# render参数转换器,返回值必须是Map
map.converters=httl.spi.Converter

# render输出转换器,返回值必须是Writer或OutputStream
out.converters=httl.spi.Converter

# 插值格式化器
formatters=httl.spi.Formatter

# 对象编解码器
codecs=httl.spi.Codec

# HTML动态插值过滤
value.filters=httl.spi.Filter

# HTML静态文本过滤
text.filters=httl.spi.Filter

# 动态插值位置切换器
value.filter.switchers=httl.spi.Switcher

# 静态文本位置切换器
text.filter.switchers=httl.spi.Switcher

# JS动态插值过滤
script.value.filters=httl.spi.Filter

# JS静态文本过滤
script.text.filters=httl.spi.Filter

# CSS动态插值过滤
style.value.filters=httl.spi.Filter

# CSS静态文本过滤
style.text.filters=httl.spi.Filter

MVC集成

HTTL在MVC中的定位:

MVC

配置查找顺序

(1) 首先查找/WEB-INF/web.xml中的context-param指定的配置:

<context-param>
    <param-name>httl.properties</param-name>
    <param-value>/WEB-INF/httl.properties</param-value>
</context-param>

(注:如果配置路径以 / 开头则表示在Web应用目录下,否则在ClassPath下查找)

(2) 如果未配置,则查找默认WEB-INF路径:/WEB-INF/httl.properties

(3) 如果WEB-INF中没有,则查找ClassPath根目录:httl.properties

(4) 如果ClassPath根目录也没有,则使用标准配置。

变量查找顺序

以${foo}为例:

(1) 首先查找当前模板内#set赋值的变量。

(2) 再查找业务Controller返回的变量。

(3) 然后查找请求属性:request.getAttribute("foo")

(4) 然后查找请求参数:request.getParameter("foo")

(5) 然后查找请求头:request.getHeader("foo")

(6) 然后查找临时会话属性:session.getAttribute("foo")

(7) 然后查找持外会话属性:cookie.get("foo")

(8) 然后查找应用属性:servletContext.getAttribute("foo")

也可以指定访问的域:

(1) ${request.foo} 返回请求属性:request.getAttribute("foo")

(2) ${parameter.foo} 返回请求参数:request.getParameter("foo")

(3) ${header.foo} 返回请求头:request.getHeader("foo")

(4) ${session.foo} 返回临时会话属性:session.getAttribute("foo")

(5) ${cookie.foo} 返回持久会话属性:cookie.get("foo")

(6) ${application.foo} 返回应用属性:servletContext.getAttribute("foo")

Servlet集成

你需要在你的业务Servlet中,处理完业务后,将业务参数都写到request.setAttribute()中。

HttlFilter会在业务Servlet执行后,从模板目录下读取与请求path同名,后缀换成.httl的模板,然后以request中的变量进行渲染。

如需执行非同名模板,可将请求forward到指定模板,HttlServlet会在模板目录下读取forward过来的path同名的模板,然后以request中的变量进行渲染。 比如:request.getRequestDispatcher("foo.httl").forward(request, response);

配置/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		version="2.5">

	<servlet>
	    <servlet-name>yourServlet</servlet-name>
	    <servlet-class>com.foo.YourServlet</servlet-class>
	    <load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
	    <servlet-name>yourServlet</servlet-name>
	    <url-pattern>*.do</url-pattern>
	</servlet-mapping>

	<servlet>
	    <servlet-name>httlServlet</servlet-name>
	    <servlet-class>httl.web.servlet.HttlServlet</servlet-class>
	    <load-on-startup>2</load-on-startup>
	</servlet>

	<servlet-mapping>
	    <servlet-name>httlServlet</servlet-name>
	    <url-pattern>*.httl</url-pattern>
	</servlet-mapping>

	<filter>
	    <filter-name>httlFilter</filter-name>
	    <filter-class>httl.web.servlet.HttlFilter</filter-class>
	</filter>

	<filter-mapping>
	    <filter-name>httlFilter</filter-name>
	    <url-pattern>*.do</url-pattern>
	</filter-mapping>

</web-app>

配置/WEB-INF/httl.properties:

import.packages+=com.your.domain
template.directory=/WEB-INF/templates
message.basename=/WEB-INF/messages
input.encoding=UTF-8
output.encoding=UTF-8
reloadable=false
precompiled=false
localized=false

配置pom.xml依赖:

<project>
    <dependencies>
        <dependency>
            <groupId>com.github.httl</groupId>
            <artifactId>httl-servlet</artifactId>
            <version>1.0.11</version>
        </dependency>
    </dependencies>
<project>

示例源码仓库:httl-servlet-demo

示例包下载参见:下载

SpringMVC集成

配置/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		version="2.5">

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

</web-app>

配置/WEB-INF/springmvc-servlet.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC"-//SPRING//DTD BEAN 2.0//EN" 
   "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean id="viewResolver" class="httl.web.springmvc.HttlViewResolver">
        <property name="contentType" value="text/html; charset=UTF-8" />
    </bean>
</beans>

配置pom.xml依赖:

<project>
    <dependencies>
        <dependency>
            <groupId>com.github.httl</groupId>
            <artifactId>httl-springmvc</artifactId>
            <version>1.0.11</version>
        </dependency>
    </dependencies>
<project>

示例源码仓库:httl-springmvc-demo

示例包下载参见:下载

Struts集成

配置/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		version="2.5">

	<filter>
		<filter-name>struts</filter-name>
		<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>struts</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

配置classpath:struts.xml:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE struts PUBLIC  
   "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"  
   "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <package name="hello" extends="httl-default">
        <action name="helloWorld" class="com.hello.HelloWorld">
            <result type="httl">/hello_world.httl</result>
		</action>
    </package>
</struts>

配置/WEB-INF/httl.properties:

import.packages+=com.your.domain
template.directory=/WEB-INF/templates
message.basename=/WEB-INF/messages
input.encoding=UTF-8
output.encoding=UTF-8
reloadable=false
precompiled=false
localized=false

配置pom.xml依赖:

<project>
    <dependencies>
        <dependency>
            <groupId>com.github.httl</groupId>
            <artifactId>httl-struts</artifactId>
            <version>1.0.11</version>
        </dependency>
    </dependencies>
<project>

示例源码仓库:httl-struts-demo

示例包下载参见:下载

Webx集成

配置/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
		version="2.5">

	<filter>
		<filter-name>webx</filter-name>
		<filter-class>com.alibaba.citrus.webx.servlet.WebxFrameworkFilter</filter-class>
		<init-param>
			<param-name>excludes</param-name>
			<param-value>*.css, *.js, *.jpg, *.gif, *.png</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>webx</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

配置/WEB-INF/webx.xml:

<?xml version="1.0" encoding="UTF-8" ?>
 Webx Root Context Configuration.  
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:services="http://www.alibaba.com/schema/services"
    xmlns:engines="http://www.alibaba.com/schema/services/template/engines"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.alibaba.com/schema/services http://localhost:8080/schema/services.xsd
        	http://www.alibaba.com/schema/services/template/engines http://localhost:8080/schema/services/template/engines.xsd">

	<services:template>
        <engines:httl-engine />
	</services:template>

</beans:beans>

配置/WEB-INF/httl.properties:

import.packages+=com.your.domain
template.directory=/WEB-INF/templates
message.basename=/WEB-INF/messages
input.encoding=UTF-8
output.encoding=UTF-8
reloadable=false
precompiled=false
localized=false

配置pom.xml依赖:

<project>
    <dependencies>
        <dependency>
            <groupId>com.github.httl</groupId>
            <artifactId>httl-webx</artifactId>
            <version>1.0.11</version>
        </dependency>
    </dependencies>
<project>