案例
近期团队中多个项目均有邮件发送功能,邮件内容采用html格式,各项目独立开发,无统一实现方案。
举例:
某类型EmailSendService
类型拥有多个String字段 content1、content2 ... content7,均为html文本
生成邮件内容直接使用字符串连接
context1 + userName + content2 + inviteCode +
content3 + money + content4 + year +
content5 + month + content6 + day +
content7
整体不足200来行的代码文件,html字符串就占去了100多行
我对这段代码的评价无疑是 负分 滚出~
存在的问题及改进点
1、Html代码混杂在java代码中,且被拆分,邮件内容及样式不易更改
2、邮件内容发送变化,则代码需要重写(正常的项目,邮件模板会发送变更是显而易见的实事)
重构
目标
1、Html模板存放于Html文件资源中,java代码以资源文件路径指定模板
2、模板支持参数替换功能,参数可以命名
半小时后重构完成,形成如下类型
文本模板类型
支持参数批量替换,以"${argName}"格式指定参数
public class TextTemplate { private String template; public TextTemplate() { setTemplate(""); } public TextTemplate(String template) { setTemplate(template); } public void setTemplate(String template) { this.template = template; } // 填充指定模板,返回结果字符串 // 模板参数替换 ${key} 替换为对应 value public String fillTemplate(LinkedHashMap<String, String> kvs) { List<String> searchList = new ArrayList<>(); List<String> replacementList = new ArrayList<>(); for (Map.Entry<String, String> entry : kvs.entrySet()) { searchList.add("${" + entry.getKey() + "}"); replacementList.add(entry.getValue()); } int size = searchList.size(); return StringUtils.replaceEachRepeatedly(template, searchList.toArray(new String[size]), replacementList.toArray(new String[size])); } }
这里使用的org.apache.commons.lang包中的StringUtils工具类,其方法replaceEachRepeatedly可以快速完成字符串替换
资源文件加载工具类
提供工具方法接受资源文件相对路径,返回资源文本
public class ResourceUtility { public static String getResourceFullText(String path) { return getResourceFullText(path, "UTF-8"); } public static String getResourceFullText(String path, String encoding) { ClassPathResource resource = new ClassPathResource(path); StringWriter writer = new StringWriter(); try { IOUtils.copy(resource.getInputStream(), writer, encoding); } catch (IOException e) { throw new RuntimeException("cannot load resource data", e); } return writer.toString(); } }
Email内容生成器类型
public class EmailHtmlContentGenerator { private TextTemplate textTemplate; public EmailHtmlContentGenerator(String resourcePath) { textTemplate = new TextTemplate(ResourceUtility.getResourceFullText(resourcePath)); } public String generateContent(LinkedHashMap<String, String> properties) { return textTemplate.fillTemplate(properties); } }
重构上层类型EmailSendService
1、抽取所有Html代码至独立的Html文件中,放置与resources目录下,可采用路径如 emailTemplates/welcome.html
2、所有表示Html代码的字符串字段删除
3、删除邮件Html代码合成逻辑,替代如下代码,独立至一个方法中
public String generateEmailContent(String userName, String invitationCode, String amount, int year, int month, int day) { LinkedHashMap<String, String> properties = new LinkedHashMap<>(); properties.put("userName", userName); properties.put("amount", amount); properties.put("year", String.valueOf(year)); properties.put("month", String.valueOf(month)); properties.put("day", String.valueOf(day)); return emailHtmlContentGenerator.generateContent(properties); }
单元测试
对TextTemplate类型进行单元测试
public class TextTemplateTests { @Test public void testFillTemplateWithNoParams() { TextTemplate textTemplate = new TextTemplate("Hello World"); assertEquals("Hello World", textTemplate.fillTemplate(new LinkedHashMap<String, String>())); } @Test public void testFillTemplateWithOneParam() { TextTemplate textTemplate = new TextTemplate("Hello ${name}"); LinkedHashMap<String, String> properties = new LinkedHashMap<>(); properties.put("name", "Ant"); assertEquals("Hello Ant", textTemplate.fillTemplate(properties)); properties.put("name", "Man"); assertEquals("Hello Man", textTemplate.fillTemplate(properties)); } @Test public void testFillTemplateWithMultiParams() { TextTemplate textTemplate = new TextTemplate("===== ${greet} ${name} ====="); LinkedHashMap<String, String> properties = new LinkedHashMap<>(); properties.put("greet", "Hello"); properties.put("name", "Ant"); assertEquals("===== Hello Ant =====", textTemplate.fillTemplate(properties)); } }
对EmailSendService类型新增方法进行单元测试
@Test public void testGenerateEmailContent() { String emailContent = emailSendService.generateEmailContent("Ant", "999.99", 2015, 2, 4); assertTrue(emailContent.contains("尊敬的Ant")); assertTrue(emailContent.contains("充值999.99元。")); assertTrue(emailContent.contains("2015年2月4日")); }