使用Java + Freemarker 导出word文档

本文介绍如何使用Freemarker结合Java实现复杂结构的Word文档导出,涵盖模板设计、数据组织、脚本替换及错误排查技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


最近的需求,有一个导出单张问卷的功能,导出为word。
网上也有很多资料,基本上来说使用freemarker模板导出的教程居多。但是基本上都是比较简单的导出对于遍历之类的问题一带而过。所以记录下自己的开发过程,以便日后查阅,也希望能帮到一些人。

本教程也是使用freemarker

前记

导出word需要有耐心,“word已损坏,无法打开”是常事,不要急。

最好第一次就将要显示的内容,结构,样式都确定下来。因为想要该样式什么的比较麻烦。

跟网上的教程一样,导出word主要分为以下几步, 新建模板,组织数据,替代

1.新建一个word模板文档,并另存为xml。
  1. 根据需求设计文档
    根据自己的需要设计好文档。 像“问卷名称”,“corpId”。在转化成xml文件后会被freemaker的脚本替换
    在这里插入图片描述

  2. 将模板另存为xml。
    对于高版本的wold,另存为xml,有两种选择。 1. word 2003 xml 文档(.xml) 2.word xml 文档(.xml)
    word2003 其实就是以.doc结尾的word文档 , word xml 就是以.docx结尾的文档。

我使用的是第一种方式。这两种格式的xml文档有些许的差别。

注意
最好使用中文作为占位符。因为使用英文的话,转为xml时,word可能会将一个单词拆分成两个,比如 我使用Title作为占位符,转化为xml后,搜索的时候一直找不到。然后你会发现,其实word将其拆分成Title。这种事也不是绝对的(同一个单词如果有不同的样式就会保存在不同的<w:r>中),所以只是建议,即便同一个单词被拆分了,也不用急等到后面就有解决方案。

3. 组织数据

通常我们会将数据保存到map中,这样配合freemarker比较方便实现数据的填充。

List<Map<String,Object>> categorys = new ArrayList<>(); //问卷的类别

Map<String,Object> cate1 =new HashMap<>(); //保存第一个类别下的试题,  (选择题)
cate1.put("category","选择题");//
List<Map<String,Object>>  issuesList1= new  ArrayList<>();  //存放问题
Map<String,Object> issues1= new HashMap<>();
issues1.put("questionDesc","你喜欢那些动漫");
issues1.put("questionType","多选");

List<Map<String,Object>> choices = new ArrayList<>(); //存放选项
Map<String,Object> choice1 =new HashMap<>();
choice1.put("optionDesc","overload");
Map<String,Object> choice2 =new HashMap<>();
choice2.put("optionDesc","七龙珠");
Map<String,Object> choice3 =new HashMap<>();
choice3 .put("optionDesc","柯南");
Map<String,Object> choice4 =new HashMap<>();
choice4 .put("optionDesc","海贼王");

choices.add(choice1);
choices.add(choice2);
choices.add(choice3);
choices.add(choice4);
issues1.put("choices",choices )
issuesList1.add(issues1);
cate1.put("issues",issuesList1);

Map<String,Object> cate2 =new HashMap<>(); //保存第二个类别下的试题, (填空题)
cate2.put("category","填空题");
List<Map<String,Object>>  issuesList2 = new  ArrayList<>();  //存放问题
Map<String,Object>  blank = new HashMap<>(); //填空题
blank.put("questionDesc","你幸福吗???");
issuesList2 .add(blank);
cate2.put("issues",issuesList2 );

categorys .add(cate1 );
categorys .add(cate2);


//根对象
Map<String,Object> map =new HashMap<>();
//答题人信息
map.put("corpId","客户编号");
map.put("corpName","客户名称");
map.put("loginName","问卷填写人姓名");
map.put("loginPhone","问卷填写人手机号");
//问卷信息
map.put("questionnaireName","问卷测试!!!"); //问卷名称.
map.put("categories",categorys );//问卷的题目

直接看代码可能会让很多人心烦,所以画了以下大概的结构

## clientInfo   问卷填写人基本信息部分
- coprId	客户编码		
- corpName  客户名称
- loginName  填写人姓名
- loginPhone  填写人名称

## questionInfo   问卷笔本信息
- questionnaireName
- categorys  (问题大类)
	 -类别1(选择题) 
		 - 选择题1
			 - 选项1
			 - 选项2
			 - 选项3
	      - 选择题2
		     -  选项1
		     - 选项2
		     - 。。。。 
     - 类别2 (填空题)
		     - 填空1
		     - 填空2
		     - 填空3


3. 使用freemaker 脚本替换,并更改文件类型

先替换还是先改文件类型,没有影响。建议还是先更改文件类型再替换。更改文件类型以后,可以使用eclipse等开发工具,编辑ftl文件,ftl文本插件有语法的高亮和错误提示,相对来说比较友好,也更容易找出错误

  1. 将之前的另存的xml模板文件,更改后缀为.ftl

  2. 搜索你之前在模板中定义的占位符。 比如问卷名称,替换为${questionnaireName}(因为我把问卷名称放在了根Map中,所以可以直接通过Key来取值)以此类推将其他内容替换。 所以使用Map组织数据对于 freemarker 来说获取数据特别方便
    在这里插入图片描述

    //根对象
    Map<String,Object> map =new HashMap<>();
    //答题人信息
    map.put("corpId","客户编号");
    map.put("corpName","客户名称");
    map.put("loginName","问卷填写人姓名");
    map.put("loginPhone","问卷填写人手机号");
    //问卷信息
    map.put("questionnaireName","问卷测试!!!"); //问卷名称.
    map.put("categories",categorys );//问卷的题目
    

注意:替换的内容需要包裹在<w:t> </w:t>之中。

word文档的结构

对于List类型的内容来说需要进行遍历。对于上面的数据结构来说,我们需要对categories,issues,choices遍历。
首先我们需要知道word xml的大概结构

<w:wordDocument>
	<w:body>
		<w:p>
			<w:pPr>
			</w:pPr>	
			<w:r>
				<w:rPr>
     						   属性:加粗,倾斜,字体颜色等
     				</w:rPr>
				<w:t> 文本内容</w:t>
			</w:r>		
		</w:p>
	</w:body>
</<w:wordDocument>
  • <w:p> 会包裹一段数据,(段落)

    • <w:pPr> 段落的属性,可选元素。 段落属性的一些示例包括对齐方式、边框、断字覆盖、缩进、行距、底纹、文本方向和孤行控制
  • <w:r> 它是具有一组共同属性(如格式设置)的文本区域。它可以包含多个<w:t>元素。如果示例文本中只有一个字是粗体,粗体将会分离到一个<w:r>中

    • <w:rPr>用于指定<w:r>属性。 连续文本属性的一些示例包括粗体、边框、字符样式、颜色、字体、字号、斜体、字距调整、禁用拼写/语法检查、底纹、小号大写字母、删除线、文字方向和下划线
  • <w:t> 实际的文本内容

    下面我们用一个例子来说明,写了一些内容,并配置了颜色
    在这里插入图片描述
    另存为xml文件后的部分代码

    <w:p wsp:rsidR="0084377C" wsp:rsidRPr="002827FA" wsp:rsidRDefault="009C2113">
    	<w:pPr>
    		<w:rPr>
    			<w:color w:val="000000"/>	
    		</w:rPr>
    	</w:pPr>
    	<w:r>
    		<w:rPr><w:rFonts w:hint="fareast"/></w:rPr>
    		<w:t>哈哈</w:t>
    	</w:r>
    	<w:r wsp:rsidRPr="009C2113">
    		<w:rPr>
    			<w:rFonts w:hint="fareast"/>
    			<w:color w:val="FF0000"/>	
    		</w:rPr>
    		<w:t></w:t>
    	</w:r>
    	<w:r wsp:rsidRPr="002827FA">
    		<w:rPr>
    			<w:rFonts w:hint="fareast"/>
    			<w:color w:val="000000"/>
    		</w:rPr>
    		<w:t>哈哈</w:t>
    	</w:r>
    </w:p>
    

    从上面可以清楚的看到,上面的内容在一个段落里包裹。同时在一个段落里可以设置多个不同的文字样式,这部分数据就会存放在 <w:r> 中,样式数据就存放在<w:rPr> 里面。

    所以说如果我们需要迭代,首先要找到你要迭代的位置在哪里?找好以后就完成了一半的工作。
    例如上面的小案例,我们需要遍历 标题 ~ 选项。 所以首先定位到 “标题” 所在的<w:p> 然后查找 “选项”所在的</w:p>。 然后将这么内容使用<#list> </#list>包裹就可以了。

在这里插入图片描述

4.添加freemarker依赖 , 测试
<dependency>
			 <groupId>org.freemarker</groupId>
   			 <artifactId>freemarker</artifactId>
    		<version>2.3.23</version>
</dependency>

下面的测试程序就可以在网上随便找了

导出Word工具类

@SuppressWarnings("deprecation")
public class ExportWordUtils {
	private Template template;
	
	/**
	* 指定模板的路径和名称
	* @param basePackage 模板路径
	* @param templateName 模板名称
	*/
	public ExportWordUtils(String basePackage, String templateName) throws Exception {
		init(basePackage, templateName);
	}
	
	/**
	* 初始化操作,创建 freemarker 模板
	*/
	private void init(String basePackage, String templateName) throws Exception {
		Configuration config = new Configuration();//需要指定版本
		config.setClassLoaderForTemplateLoading(ExportWordUtils.class.getClassLoader(), basePackage);//去那个文件夹下寻找模板文件
		config.setOutputEncoding("utf-8");
		template = config.getTemplate(templateName);//模板名称获取模板
	}
	
	/**
	* 实际文件导出
	*@param out 输出流
	*@param results,构造的输出结果
	*/
	public <K, V> void doExport(OutputStream out, Map<K, V> results) throws TemplateException, IOException {
		template.process(results, new OutputStreamWriter(out));//对模板进行数据填充
	}
	
	/**
	* 设置文件下载的文件名,防止中文乱码
	*/
	public void setHeader(HttpServletRequest request, HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
		String userAgent = request.getHeader("User-Agent");
		if(StringUtils.contains(userAgent, "MSIE")||StringUtils.contains(userAgent, "Trident") ||
		StringUtils.contains(userAgent, "Edge")){ //解决IE中文名称乱码
			fileName = URLEncoder.encode(fileName,"UTF8");
		}else if(StringUtils.contains(userAgent, "Firefox")){//火狐和其他浏览器中文名乱码
			fileName = new String(fileName.getBytes("utf8"), "ISO8859-1");
		}
		 response.setContentType("application/octet-stream";charset=utf-8");
		 response.setHeader("Content-Disposition", "attachment;" + " filename=\""+fileName+"\"");
	}
}

导出请求

	@RequestMapping("/exportWorld")
	public ResultInfo exportWorld(String param,HttpServletRequest request,HttpServletResponse response) {
		OutputStream out=null;
		try {
			out = response.getOutputStream();
			Map<String,Object> reuslts =questionnaireService.getAnswers(param); //获取问卷信息,就是上面的结构
			if(reuslts==null)
				return ResultInfo.error("问卷导出失败!");
			/* 
			*项目使用的spring boot 。/excel/questionnaire.ftl文件存放在src/main/resources目录下
			*/
			ExportWordUtils word =new ExportWordUtils("/excel", "questionnaire.ftl"); 
			word.setHeader(request, response, reuslts.get("questionName")+".doc");
			word.doExport(out, reuslts);
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(out!=null)
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
		return null;
	}
		
错误排查

上面的代码每次请求都会下载一个word文件。但是使用该方式来测试还是太麻烦。

程序不报错的话只能说明数据请求, freemarker数据替换和渲染没有问题,但是Word的内容结构是否完整这是不确定的(xml的格式要求比html 严格)。

所以测试的时候,我会将下面的代码注释掉,

//word.setHeader(request, response, reuslts.get("questionName")+".doc");

返回内容就是字节流。浏览器是支持xml字节流的渲染,当该文件结构不完整时,就会报错。然后调整我们的代码就可以了
在这里插入图片描述
比如,我在第30行,增加了一个多余的 <
在这里插入图片描述
请求之后就会抛出下面的错误,这样比较容易定位问题。
在这里插入图片描述
但是有些问题并不那么容易定位。尤其是遍历元素的时候,位置没有确定好,就不太好定位。

在这里插入图片描述
在这里插入图片描述

这时候我们可以将字节流在控制台输出。

//			out = response.getOutputStream();
			out =new ByteArrayOutputStream();
			Map<String,Object> reuslts =questionnaireService.getQuestionnaireAnswer(questionnaireId,corpIdGroup,corpId,loginCode,sourceCode);
			if(reuslts==null)
				return ResultInfo.error("问卷导出失败!");
			ExportWordUtils word =new ExportWordUtils("/excel", "questionnaire.ftl");
//			word.setHeader(request, response, reuslts.get("questionName")+".doc");
			word.doExport(out, reuslts);			
			System.out.println(out);

将输出的数据在合适的编辑器中打开,找到报错的位置。可以选择在线xml校验等具有xml校验功能的工具,这样更容易定位问题
在这里插入图片描述

问题总结

  1. 当导出word提示 “无法打开文件,内容有错误” ,但是实际数据已经替换完成。这个问题通常就是,不小心修改了word xml的结构,导致xml标签没有正常闭合 或者 填充的内容中存在<。尤其使用<#if> <#list> 等控制语句时尤其容易出现标签无法闭合的问题,所以要特别注意,这些语句的包裹范围。必要的话需要对填充的内容(特指 <)进行转码。

  2. 如果在word 模板中使用的占位元素,被拆分了怎么办?下面就是使用Title作为占位符,但是另存在xml时,被拆分了

       <w:p wsp:rsidR="00062FF5" wsp:rsidRDefault="00E640FA" wsp:rsidP="00062FF5"><w:pPr><w:pStyle w:val="a3"/><w:jc w:val="center"/><w:rPr><w:b/>
       <w:sz w:val="44"/><w:sz-cs w:val="44"/></w:rPr></w:pPr>
       <w:r><w:rPr><w:rFonts w:hint="fareast"/><w:b/><w:sz w:val="44"/><w:sz-cs w:val="44"/></w:rPr>
       <w:t>`T`</w:t></w:r>
       <w:r><w:rPr><w:b/><w:sz w:val="44"/><w:sz-cs w:val="44"/></w:rPr>
       <w:t>`itle`</w:t></w:r>
       </w:p>
    

    很简单将多余的<w:r> … </w:r>删除就好了,将T 所在的<w:r>删除就可以了。

  3. 不会freemarker怎么办? 。freemarker中文文档,点击就送

Freemarker页面语法 A 概念 最常用的 3 个概念 sequence 序列,对应java 里的list 、数组等非键值对的集合 hash 键值对的集合 namespace 对一个ftl 文件的引用, 利用这个名字可以访问到该ftl 文件的资源 B 指令 if, else, elseif 语法 Java代码 ... ... ... ... ... ... ... ... ... ... 用例 Freemarker代码 x is 1 x is 1 x is not 1 x is 1 x is 1 x is not 1 switch, case, default, break 语法 Freemarker代码 ... ... ... ... ... ... ... ... ... ... 用例 字符串 Freemarker代码 This will be processed if it is small This will be processed if it is medium This will be processed if it is large This will be processed if it is neither This will be processed if it is small This will be processed if it is medium This will be processed if it is large This will be processed if it is neither 数字 Freemarker代码 1 2 d 1 2 d 如果x=1 输出 1 2, x=2 输出 2, x=3 输出d list, break 语法 Freemarker代码 ... ... ... ... 关键字 item_index:是list当前值的下标 item_has_next:判断list是否还有值 用例 Freemarker代码 ${x_index + 1}. ${x}, ${x_index + 1}. ${x}, 输出: 1.winter, 2.spring, 3.summer, 4.autumn include 语法 Freemarker代码 或则 Java代码 options包含两个属性 encoding="GBK" 编码格式 parse=true 是否作为ftl语法解析,默认是true,false就是以文本方式引入.注意在ftl文件里布尔值都是直接赋值 的如parse=true,而不是parse="true" 用例 /common/copyright.ftl 包含内容 Ftl代码 Copyright 2001-2002 ${me} All rights reserved. Copyright 2001-2002 ${me} All rights reserved. 模板文件 Java代码 Some test Yeah ___________________________________________________________________________ Some test Yeah ___________________________________________________________________________ 输出结果: Some test Yeah. Copyright 2001-2002 Juila Smith All rights reserved. Import 语法 Freemarker代码 类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件 用例 假设mylib.ftl 里定义了宏copyright 那么我们在其他模板页面里可以这样使用 Freemarker代码 <#-- "my"在freemarker里被称作namespace --> <#-- "my"在freemarker里被称作namespace --> compress 语法 Freemarker代码 ... ... 用来压缩空白空间和空白的行 escape, noescape 语法 Freemarker代码 ... ... ... ... ... ... 用例 主要使用在相似的字符串变量输出,比如某一个模块的所有字符串输出都必须是html安全的,这个时候就可以使用 该表达式 Freemarker代码 First name: ${firstName} Last name: ${lastName} Maiden name: ${maidenName} First name: ${firstName} Last name: ${lastName} Maiden name: ${maidenName} 相同表达式 Ftl代码 First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} assign 语法 Freemarker代码 <#-- 或则 --> <#-- 或则 --> <#-- 或则 --> capture this <#-- 或则 --> capture this <#-- 或则 --> <#-- 或则 --> <#-- 或则 --> capture this <#-- 或则 --> capture this 用例 生成变量,并且给变量赋值 给seasons赋予序列值 Ftl代码 给变量test加1 Ftl代码 给my namespage 赋予一个变量bgColor,下面可以通过my.bgColor来访问这个变量 Ftl代码 将一段输出的文本作为变量保存在x里 Ftl代码 ${n} Number of words: ${x?word_list?size} ${x} Hello ${user}! error true ${n} Number of words: ${x?word_list?size} ${x} Hello ${user}! error true 同时也支持中文赋值,如: Ftl代码 java ${语法} java ${语法} 打印输出: java global 语法 Freemarker代码 <#--或则--> <#--或则--> capture this <#--或则--> <#--或则--> capture this 全局赋值语法,利用这个语法给变量赋值,那么这个变量在所有的namespace [A1] 中是可见的, 如果这个变量被当前的assign 语法覆盖 如 在当前页面里x=2 将被隐藏,或者通过${.global.x} 来访问 setting 语法 Freemarker代码 用来设置整个系统的一个环境 locale number_format boolean_format date_format , time_format , datetime_format time_zone classic_compatible 用例 假如当前是匈牙利的设置,然后修改成美国 Ftl代码 ${1.2} ${1.2} ${1.2} ${1.2} 输出 1,2 1.2 因为匈牙利是采用", "作为十进制的分隔符,美国是用". " macro, nested, return 语法 Freemarker代码 ... ... ... ... ... ... 用例 Ftl代码 Test text, and the params: ${foo}, ${bar}, ${baaz} Test text, and the params: ${foo}, ${bar}, ${baaz} 输出 Test text, and the params: a, b, 23 Test text, and the params: a, b, -1 Test text, and the params: a, Bar, 23 Test text, and the params: a, Bar, -1 定义循环输出的宏 Ftl代码 ${title?cap_first}: *${x?cap_first} ${title?cap_first}: *${x?cap_first} 输出结果: Animals: *Mouse *Elephant *Python 包含body 的宏 Ftl代码 ${c}. ${halfc} Last! ${c}. ${halfc} Last! 输出 1. 0.5 2. 1 3. 1.5 4. 2 Last! t, lt, rt 语法 Freemarkder代码 去掉左右空白和回车换行 去掉左边空白和回车换行 去掉右边空白和回车换行 取消上面的效果 去掉左右空白和回车换行 去掉左边空白和回车换行 去掉右边空白和回车换行 取消上面的效果 C 一些常用方法或注意事项 表达式转换类 ${expression} 计算expression 并输出 #{ expression } 数字计算#{ expression ;format} 安格式输出数字format 为M 和m M 表示小数点后最多的位数,m 表示小数点后最少的位数如#{121.2322;m2M2} 输出121.23 数字循环 1..5 表示从1 到5 ,原型number..number 对浮点取整数 ${123.23?int} 输出 123 给变量默认值 ${var?default("hello world")?html} 如果var is null 那么将会被hello world 替代 判断对象是不是 null Ftl代码 Mouse found Mouse found 也可以直接${mouse?if_exists})输出布尔形 -------------------------------------------- (1)解决输出中文乱码问题: freemarker乱码的原因: 没有使用正确的编码格式读取模版文件,表现为模版中的中文为乱码 解决方法:在classpath上放置一个文件freemarker.properties,在里面写上模版文件的编码方式,比如 default_encoding=UTF-8 locale=zh_CN 注意:eclipse中除了xml文件、java文件外,默认的文件格式iso8859-1 数据插入模版时,没有使用正确的编码,表现出模版中的新插入数据为乱码 解决方法:在result的配置中,指定charset,s2的FreemarkerResult.java会将charset传递freemarker /pages/Person/view.ftl text/html;charset=UTF-8 (2)提高freemarker的性能 在freemarker.properties中设置: template_update_delay=60000 避免每次请求都重新载入模版,即充分利用cached的模版 (3)尽量使用freemarker本身的提供的tag,使用S2 tags 的标签会在性能上有所损失 (4)freemarker的标签种类: ${..}:FreeMarker will replace it in the output with the actual value of the thing in the curly brackets. They are called interpolation s. # ,代表是FTL tags(FreeMarker Template Language tags) ,hey are instructions to FreeMarker and will not be printed to the output ... @ ,代表用户自定义的标签 <#-- --> 注释标签,注意不是<!-- --> (5)一些特殊的指令: r代表原样输出:${r"C:\foo\bar"} ${x} ?引出内置指令 String处理指令: html:特殊的html字符将会被转义,比如"<",处理后的结果是< cap_first 、lower_case 、upper_case trim :除去字符串前后的空格 sequences处理指令 size :返回sequences的大小 numbers处理指令 int:number的整数部分,(e.g. -1.9?int is -1) (6)对于null,或者miss value,freemarker会报错 ?exists:旧版本的用法 !:default value operator,语法结构为: unsafe_expr !default_expr,比如 ${mouse!"No mouse."} 当mouse不存在时,返回default value; (product.color)!"red" 这种方式,能够处理product或者color为miss value的情况; 而product.color!"red"将只处理color为miss value的情况 ??: Missing value test operator ,测试是否为missing value unsafe_expr ?? :product.color??将只测试color是否为null (unsafe_expr )??:(product.color)??将测试product和color是否存在null Ftl代码 <#if mouse??> Mouse found No mouse found Creating mouse... <#if mouse??> Mouse found No mouse found <#if mouse??> Mouse found No mouse found Creating mouse... <#if mouse??> Mouse found No mouse found (7)模版值插入方式 (interpolation) 通用方式 ( Universal interpolations): ${expression } 对于字符串:只是简单输出 对于数值,会自动根据local确定格式,称为human audience,否则称为computer audience,可以"?c", 比如, Details...,因此这里的id是给浏览器使用的,不需要进行格式化,注意?c只对数值有效 对于日期,会使用默认的日期格式转换,因此需要事先设置好默认的转换格式,包括date_format , time_format ,atetime_format 对于布尔值,不能输出,会报错并停止模版的执行,比如${a = 2} 会出错,但是可以 string built-in来进行转换 数值处理,具体参考:Built-ins for numbers http://freemarker.org/docs/ref_builtins_number.html#ref_builtin_string_for_number 数值处理的例子: ${answer} ${answer?string} <#-- the same as ${answer} --> ${answer?string.number} ${answer?string.currency} ${answer?string.percent} 除了使用内置的formate,可以使用任何用Java decimal number format syntax 书写的formate,比如 ${1234} ${12345?string("0.####E0")} 更加方便的格式: US people writes: ${12345678?string(",##0.00")} Hungarian people writes: ${12345678?string(",##0.00")} 日期处理,参考Built-ins for dates http://freemarker.org/docs/ref_builtins_date.html#ref_builtin_string_for_date 日期处理的例子: ${openingTime?string.short} ${openingTime?string.medium} ${openingTime?string.long} ${openingTime?string.full} ${nextDiscountDay?string.short} ${nextDiscountDay?string.medium} ${nextDiscountDay?string.long} ${nextDiscountDay?string.full} ${lastUpdated?string.short} ${lastUpdated?string.medium} ${lastUpdated?string.long} ${lastUpdated?string.full} 注意: 由于java语言中的Date类型的不足,freemarker不能根据Date变量判断出变量包含的部分(日期、时间还是全部),在这种情况下,freemarker 不能正确显示出${lastUpdated?string.short} 或者 simply ${lastUpdated},因此,可以通过?date, ?time and ?datetime built-ins 来帮助freemarker来进行判断,比如${lastUpdated?datetime?string.short} 除了使用内置的日期转换格式外,可以自己指定日期的格式,使用的是Java date format syntax,比如: ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} 数值专用方式 ( Numerical interpolations):#{expression } or #{expression ; format },这是数值专用的输出方式,但是 最好使用通用方式的string built-in或者number_format 来完成转换,Numerical interpolations方式将会被停用 (8)创建自定义模版 Ftl代码 Hello Joe! 评论这张 转发至微博
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值