superword这个项目,全使用JAVA8新特性: https://github.com/ysc/superword ,一开始只是我的一个英语单词分析工具,用于生成HTML片段然后发到博客中,后来功能越来越强于是我就做成一个项目了,再后来有人跟我说自己不是计算机专业的不会用这个软件,于是我就改造成了一个WEB项目,这个项目现在有点需要改进的地方,就是把JAVA代码生成HTML的这个逻辑改成使用FREEMARKER的方式。
我们首先来看在org.apdplat.superword.system.AntiRobotFilter类中的原来的JAVA代码生成HTML的逻辑:
1StringBuilder html = new StringBuilder(); 2html.append("<h1>The meaning of red color font is your answer, but the right answer is the meaning of blue color font for the word <font color=\"red\">") 3 .append(quizItem.getWord().getWord()) 4 .append(":</font></h1>"); 5html.append("<h2><ul>"); 6for(String option : quizItem.getMeanings()){ 7 html.append("<li>"); 8 if(option.equals(_answer)) { 9 html.append("<font color=\"red\">").append(option).append("</font>"); 10 }else if(option.equals(quizItem.getWord().getMeaning())){ 11 html.append("<font color=\"blue\">").append(option).append("</font>"); 12 }else{ 13 html.append(option); 14 } 15 html.append("</li>\n"); 16} 17html.append("</ul></h2>\n<h1><a href=\"") 18 .append(servletContext.getContextPath()) 19 .append("\">Continue...</a></h1>\n");
这样的代码对JAVA开发人员来说,第一次写的时候很爽很方便,用于原型开发快速验证功能是可以的,不过如果隔的时间长了自己再回头来看或者其他人来看这段代码,就会很吃力,因为这里纠缠了JAVA和HTML,纠缠了业务逻辑、数据处理逻辑以及显示逻辑,所以,如果代码需要持续维护的话就需要重构,下面我们就使用FREEMARKER来重构。
第一步,在pom.xml中引入FREEMARKER的依赖:
1<!-- html模板引擎 --> 2<dependency> 3 <groupId>org.freemarker</groupId> 4 <artifactId>freemarker</artifactId> 5 <version>${freemarker.version}</version> 6</dependency> 7 8<freemarker.version>2.3.24-incubating</freemarker.version>
第二步,在类路径下的template/freemarker/identify_quiz.ftlh文件中定义HTML模板:
1<h1> 2 The meaning of red color font is your answer, but the right answer is the meaning of blue color font for the word <font color="red">${quizItem.word.word}:</font> 3</h1> 4<h2> 5 <ul> 6<#list quizItem.meanings as meaning> 7 <#if meaning == answer> 8 <#-- 用户答案 --> 9 <li><font color="red">${meaning}</font></li> 10 <#elseif meaning == quizItem.word.meaning> 11 <#-- 正确答案 --> 12 <li><font color="blue">${meaning}</font></li> 13 <#else> 14 <#-- 其他选项 --> 15 <li>${meaning}</li> 16 </#if> 17</#list> 18 </ul> 19</h2> 20<h1> 21 <a href="">Continue...</a> 22</h1>
第三步,在org.apdplat.superword.system.AntiRobotFilter类中准备模板需要的数据:
1Map<String, Object> data = new HashMap<>(); 2data.put("quizItem", quizItem); 3data.put("answer", _answer);
第四步,编写一个工具类org.apdplat.superword.freemarker.TemplateUtils,将模板和数据融合生成最终的HTML代码:
1package org.apdplat.superword.freemarker; 2 3import freemarker.template.Configuration; 4import freemarker.template.Template; 5import freemarker.template.TemplateExceptionHandler; 6import org.apdplat.superword.model.QuizItem; 7import org.slf4j.Logger; 8import org.slf4j.LoggerFactory; 9 10import java.io.StringWriter; 11import java.io.Writer; 12import java.util.HashMap; 13import java.util.Map; 14 15/** 16 * 模板工具, 用于生成html代码 17 * Created by ysc on 4/2/16. 18 */ 19public class TemplateUtils { 20 private TemplateUtils(){} 21 private static final Logger LOGGER = LoggerFactory.getLogger(TemplateUtils.class); 22 private static final Configuration CFG = new Configuration(Configuration.VERSION_2_3_23); 23 24 static{ 25 LOGGER.info("开始初始化模板配置"); 26 CFG.setClassLoaderForTemplateLoading(TemplateUtils.class.getClassLoader(), "/template/freemarker/"); 27 CFG.setDefaultEncoding("UTF-8"); 28 if(LOGGER.isDebugEnabled()) { 29 CFG.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); 30 }else{ 31 CFG.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER); 32 } 33 CFG.setLogTemplateExceptions(false); 34 LOGGER.info("模板配置初始化完毕"); 35 } 36 37 /** 38 * 在识别用户是否是机器人的测试中, 如果用户测试失败, 则向用户显示这里生成的HTML代码 39 * @param data 需要两个数据项, 一是测试数据集quizItem, 二是用户的回答answer 40 * @return 测试结果HTML代码 41 */ 42 public static String getIdentifyQuiz(Map<String, Object> data){ 43 try { 44 Template template = CFG.getTemplate("identify_quiz.ftlh"); 45 Writer out = new StringWriter(); 46 template.process(data, out); 47 return out.toString(); 48 }catch (Exception e){ 49 LOGGER.error("generate authentication template failed", e); 50 } 51 return ""; 52 } 53 54 public static void main(String[] args) { 55 Map<String, Object> data = new HashMap<>(); 56 QuizItem quizItem = QuizItem.buildIdentifyHumanQuiz(12); 57 data.put("quizItem", quizItem); 58 data.put("answer", "random answer"); 59 System.out.println(TemplateUtils.getIdentifyQuiz(data)); 60 } 61}
第五步,在org.apdplat.superword.system.AntiRobotFilter类中删除JAVA代码生成HTML的逻辑,转而使用如下代码:
1Map<String, Object> data = new HashMap<>(); 2data.put("quizItem", quizItem); 3data.put("answer", _answer); 4String html = TemplateUtils.getIdentifyQuiz(data);
大功告成!看一下页面输出效果:

最后看一下模板引擎的日志输出,第一次访问:
1开始初始化模板配置 2模板配置初始化完毕 30 DEBUG [2016-04-02 22:04:25] Couldn't find template in cache for "identify_quiz.ftlh"("en_US", UTF-8, parsed); will try to load it. 41 DEBUG [2016-04-02 22:04:25] TemplateLoader.findTemplateSource("identify_quiz_en_US.ftlh"): Not found 52 DEBUG [2016-04-02 22:04:25] TemplateLoader.findTemplateSource("identify_quiz_en.ftlh"): Not found 62 DEBUG [2016-04-02 22:04:25] TemplateLoader.findTemplateSource("identify_quiz.ftlh"): Found 72 DEBUG [2016-04-02 22:04:25] Loading template for "identify_quiz.ftlh"("en_US", UTF-8, parsed) from "file:/Users/ysc/workspace/superword/target/superword-1.0/WEB-INF/classes/template/freemarker/identify_quiz.ftlh"
第二次:
15324 DEBUG [2016-04-02 22:04:30] TemplateLoader.findTemplateSource("identify_quiz_en_US.ftlh"): Not found 25325 DEBUG [2016-04-02 22:04:30] TemplateLoader.findTemplateSource("identify_quiz_en.ftlh"): Not found 35325 DEBUG [2016-04-02 22:04:30] TemplateLoader.findTemplateSource("identify_quiz.ftlh"): Found 45325 DEBUG [2016-04-02 22:04:30] "identify_quiz.ftlh"("en_US", UTF-8, parsed): using cached since file:/Users/ysc/workspace/superword/target/superword-1.0/WEB-INF/classes/template/freemarker/identify_quiz.ftlh hasn't changed.
第三次:
181642 DEBUG [2016-04-02 22:05:47] TemplateLoader.findTemplateSource("identify_quiz_en_US.ftlh"): Not found 281643 DEBUG [2016-04-02 22:05:47] TemplateLoader.findTemplateSource("identify_quiz_en.ftlh"): Not found 381643 DEBUG [2016-04-02 22:05:47] TemplateLoader.findTemplateSource("identify_quiz.ftlh"): Found 481643 DEBUG [2016-04-02 22:05:47] "identify_quiz.ftlh"("en_US", UTF-8, parsed): using cached since file:/Users/ysc/workspace/superword/target/superword-1.0/WEB-INF/classes/template/freemarker/identify_quiz.ftlh hasn't changed.
这次重构的完整代码见:https://github.com/ysc/superword/commit/a46b48a352106143ce3a10964b1a98f45a961944,superword中还有一些地方需要做类似的重构,有兴趣的同学可以尝试一下,测试成功后欢迎在github上面给我发pull request.