freemarker自定义指令及方法

一节复一节,千枝攒万叶”竹子每生长一段就会总结一下打个节,今天我也总结一下最近处理的一个小问题,希望我的小节,对各位看官有所帮助。

背景描述:

最近项目做了一个月报功能,由于生产环境不是所有人都能访问,“皇上”提出要求要求把月报方式到订阅者的邮箱。亲需求一段话,开发两行泪啊。这种问题本人想到的方案也就两种,第一将网页保存为图片然后发送邮件,第二生成静态页面然后发送邮件。第一种需要一下人工参与,所以选择了第二种方式。“老奴”也是过来人,自然想到用freemarker生成页面。当然除了静态页面直接访问功能还是有必要保留的,由于项目报表采用echarts实现,数据为异步请求到的结果,前段js还包含部分数据处理逻辑,为了便于维护和减少修改只能将数据提取与业务处理分离,遂使用jsp自定义标签实现取数方法。进展无任何障碍轻松实现。

接下来到了生成静态页面的实现了。编写ftl模板文件、准备数据、编写代码生成html。问题来了,编写模板的时候就心生疑惑,“自定义标签在freemarker中怎么处理啊?”,用眼睛执行代码都能知道有错。遂百度+google freemarker中如何使用jsp自定义标签(估计是大多数人第一直觉)。果然有结果。大多方案如下

  1. <#assign sci=JspTaglibs["/WEB-INF/tld/XXX.tld"]>  
  2. <@sci.articleList type="03" num="5" />

按照步骤进行配置、启动、生成

The following has evaluated to null or missing:
==> JspTaglibs  [in template "report.ftl" at line 1, column 12]
----
Tip: The "JspTaglibs" variable isn't a core FreeMarker feature; it's only available when templates are invoked through freemarker.ext.servlet.FreemarkerServlet (or other custom FreeMarker-JSP integration solution).

然后继续百度,google仍然无解,最后终于在Stack Overflow找到了点眉目,果然也和我之前心中的疑惑有关。jsp自定义标签是jsp的内容,jsp在运行的时候会编译成类似servlet的.class文件,自定义标签调用的就是标签类的相关代码,而且自定义标签类扩展自TagSupport,然后可以获取到pageContext,pageContext是个位于牛A与牛C之间的对象啊,四种作用域都能操作啊。那么不仅让我心生疑问啊,freemarker整合它得时候是怎么做到的那。换而言之我的程序是用定时任务触发的随运行在web程序中,但是应该只有application作用域是有效的啊。现在来看只能带着我的疑惑找官方文档啦,无奈英语实在蹩脚啊,在有道的帮助下终于找到了相关文章看懂了个大概,后来又找到了freemarker的中文翻译文档(分享在我的资源中)。英文链接,中文文档中为pgui_misc_servlet.html页面详细介绍了如何在servlet中使用freemarker。实际上个人觉得这里介绍的内容是如何使用freemarker替换jsp,其中提及了在ftl中使用jsp自定义标签。freemarker在整合jsp的自定义标签是需要使用一个其内部的servlet,然后该servlet的mapping过滤*.ftl请求,同时还需配置freemarker提供的监听EventForwarding,显然我的业务特点并不能使用此方案实现。所以唯有采用其他技术思路了。

freemarker整合JspTaglibs的技术路线行不通,就只能找类似jsp的解决方法了,自定义一个ftl指令。

<#reqData types="0101">
 指令体
</#reqData>

时至于此我手头的资料也是比较全了,翻看文档很快就找到了自定义指令,在freemarker中用java实现自定义指令很容易,创建一个类实现TemplateDirectiveModel接口,重写execute方法即可,

public void execute(Environment env, Map params, TemplateModel[] loopVars,
            TemplateDirectiveBody body) throws TemplateException, IOException {

该方法有三个参数,第一个参数可获取env可获取到一个输出流,第二个参数为指令中的参数,例如我需要的指令中的types。第三个参数TemplateModel我没有用上,第四个TemplateDirectiveBody为指令体内容的封装。body有个一个核心方法为render用于构造指令生成的内容,但是由于我的指令时间上是不需要指令体的,所以在程序中body为null。如果将我的指令写成<#reqData types="0101">XXX填充字符符</#repData>显然很恶心,事实证明再次选择错误。

  继续翻看资料果然找到了更适合的方式,那就是自定义方法

自定义方法可通过实现TemplateMethodModelEx接口的方式实现,重写exec方法,该方法参数为List args,即方法调用时候的参数。使用时更简单。对于我获取数据的函数即变为${reqData("0101")}.

 补充,说到这里各位亲可能会比较疑惑,那么自定义的方法或指令是如何在freemarker中生效的那?这个很简单,直需要在你根据模板生成数据的时候在map类型的参数中添加即可。例如root.put("reqData", new XXXMethod|Model)即可。

例如我的示例代码

package com.asia.util.tag;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import com.asia.report.bean.EChart;
import com.asia.report.bean.RepIndicatorVal;
import com.asia.report.service.IRepIndicatorValService;
import com.fasterxml.jackson.databind.ObjectMapper;

import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;

/**
 * FreeMarker获取报表数据标签定义
 * 
 * @author SCY
 * 
 * @CreateTime 2016年5月11日下午3:23:48
 * 
 * @Vsersion 1.0
 */
public class RepDataTemplate implements TemplateMethodModelEx {

	//获取数据的Service,当然需要用构造方法传递过来啦
	private IRepIndicatorValService repIndicatorValService;

	public RepDataTemplate(IRepIndicatorValService repIndicatorValService) {
		this.repIndicatorValService = repIndicatorValService;
	}

	@Override
	public Object exec(@SuppressWarnings("rawtypes") List arguments) throws TemplateModelException {
		try {
			RepIndicatorVal rv = new RepIndicatorVal();
			rv.setTypes(arguments.get(0).toString());
			rv.setStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
			List<EChart> res = repIndicatorValService.queryRepIndicatorVal(rv);
			Object obj = res.size() == 1 ? res.get(0) : res;
			String str = new ObjectMapper().writeValueAsString(obj);
			return str;
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}

	}

}

HTML生成代码

public void constructorStaticHTML() throws Exception{
		//用于保存freemarker模板数据
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("repData", new RepDataTemplate(this));
		//获取模板路径
		WebappTemplateLoader templateLoader = new WebappTemplateLoader(ContextLoader.getCurrentWebApplicationContext().getServletContext(), "WEB-INF/ftl");
		templateLoader.setURLConnectionUsesCaches(false);
		templateLoader.setAttemptFileAccess(false);
		Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
		cfg.setTemplateLoader(templateLoader);
		Template template = cfg.getTemplate("report.ftl", "utf-8");
		Writer writer  = new OutputStreamWriter(new FileOutputStream("d:/report/success.html"),"UTF-8");
		template.process(param, writer);
		writer.close();
		
}

欢迎大家拍砖 大笑

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值