最近,在开发工作中有这么的需求,使用模板技术加javax mail发送模板邮件,经历的一系列的蛋疼时刻终于搞定,在这里做下总结,以备以后在遇到类似问题,方便查找。
使用VelocityEngine + Spring完成模板解析,VelocityTemplateMailProcessor.java:
/**
* 处理velocity邮件模板,将template中参数替换为真实值
*
* @author Arthur
*
*/
public class VelocityTemplateMailProcessor implements TemplateMailProcessor {
private final static Logger LOGGER = Logger
.getLogger(VelocityTemplateMailProcessor.class);
/**
* 使用velocity模板引擎处理邮件模板,支持在velocity等模板文件中对时间和数字进行格式化支持
*
* @param templateLocation
* 邮件模板路径:例如在src/main/resoures下有一个example.tpl文件,
* 则起对应的tamplateLocation为example.tpl
* @param charsetEncoding
* 读取邮件模板使用的charset
* @param attributeMap
* 模板中对应占位符的值map
* @return
*/
public String process(String templateLocation, String charsetEncoding,
Map<String, Object> attributeMap) {
// First, volidate attributeMap, make sure "dateTool" & "numberTool" not
// used by application
this.validate(attributeMap);
try {
Properties prop = new Properties();
prop.put("file.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
// 增加date和number格式化支持
try {
VelocityTemplateMailProcessor.class.getClassLoader().loadClass(
"org.apache.velocity.tools.generic.DateTool");
LOGGER.info("DateTool 'org.apache.velocity.tools.generic.DateTool' found in classpath and support date format");
DateTool dateTool = new DateTool();
attributeMap.put("dateTool", dateTool);
} catch (ClassNotFoundException e) {
// not exist, just skip
}
try {
VelocityTemplateMailProcessor.class.getClassLoader().loadClass(
"org.apache.velocity.tools.generic.NumberTool");
LOGGER.info("NumberTool 'org.apache.velocity.tools.generic.NumberTool' found in classpath and support numberic format");
NumberTool numberTool = new NumberTool();
attributeMap.put("numberTool", numberTool);
} catch (ClassNotFoundException e) {
// not exist, just skip
}
VelocityEngineFactoryBean factoryBean = new VelocityEngineFactoryBean();
factoryBean.setVelocityProperties(prop);
VelocityEngine velocityEngine = factoryBean.createVelocityEngine();
LOGGER.debug("VelocityEngine initialization is ok");
return VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,
templateLocation, charsetEncoding, attributeMap);
} catch (Exception e) {
LOGGER.error("use VelocityEngine to process mail template["
+ "templateLocation" + "] error", e);
throw new ParseTemplateException(e);
}
}
private void validate(Map<String, Object> attributeMap) {
if (attributeMap.get("dateTool") != null) {
throw new IllegalArgumentException(
"dateTool is reserved field not for application, Please make sure it's not used");
}
if (attributeMap.get("numberTool") != null) {
throw new IllegalArgumentException(
"numberTool is reserved field not for application, Please make sure it's not used");
}
}
}
ok,使用该类完成邮件模板的解析工作,接着发送邮件,邮件发送类这里就不贴出来了;
使用javax mail发送邮件时最容易遇到的问题就是邮件内容和标题的乱码,对于内容乱码,相信很多人都遇到过,也都知道怎么解决。
至于标题乱码有点棘手,需要对邮件title进行Base64编码,防止邮件标题中文乱码,但是这里就需要对使用哪个Base64实现类进行选择,
不幸的是开始楼主选择了Sun的sun.misc.BASE64Encoder进行encode,结果发现有的邮件乱码,有的邮件正常;好蛋疼!!
经过楼主的排查,翻阅javax mail源码(MimeMessage、MimeUtility.java、PropUtil.java)发现在MimeMessage.java中:
public void setSubject(String subject, String charset)
throws MessagingException {
if (subject == null) {
removeHeader("Subject");
} else {
try {
setHeader("Subject", MimeUtility.fold(9,
MimeUtility.encodeText(subject, charset, null)));
} catch (UnsupportedEncodingException uex) {
throw new MessagingException("Encoding error", uex);
}
}
}
该方法调用到了MimeUtility.fold()方法
public static String fold(int used, String s) {
if (!foldText)
return s;
int end;
char c;
// 请注意这里
// Strip trailing spaces and newlines
for (end = s.length() - 1; end >= 0; end--) {
c = s.charAt(end);
if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
break;
}
if (end != s.length() - 1)
s = s.substring(0, end + 1);
// 一下省略啦
......
}
该方法遇到""、\t、\r、\n就会将邮件标题截取,剩余部分默认将其作为邮件内容;
回过头来,再看下sun.misc.BASE64Encoder.encode(),sun的base64实现完全遵循了Base64(RFC2045~RFC2049)规范,将加密后的字符串每76个字符后插入回车换行符,这就导致了邮件标题base64之后不完整,而且邮件内容多了一部分,导致邮件无法被解析展示。
最后楼主经过测试使用了apache的Base64实现org.apache.commons.codec.binary.Base64,该实现对加密后的Base64字符串不进行任何回车换行的操作。
OK,这里把解决邮件标题乱码的部分代码贴出来:
String subject = new String(Base64.encodeBase64("subject".getBytes("UTF-8")));
mailMessage.setSubject("=?UTF-8?B?" + subject + "?=");
第一次写博客,写的不好还请轻拍:)