最近,在开发工作中有这么的需求,使用模板技术加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 + "?=");
第一次写博客,写的不好还请轻拍:)