最近刚完成一个任务-付款提醒邮件的发送,对于java邮件的发送有了更深刻的认识,的确java提供的邮件发送机制的确让邮件发送这个问题变得灵活而又简单。并且由于项目组其他人负责了短信的发送,巧的是这个邮件发送的借口与短信发送的借口都被封装到了消息发送的借口,我也顺便学习了一下短信发送的原理,呵呵,算是一箭双雕吧。
那先来说说邮件发送。按照按接口编程的习惯,当然先要定义一个邮件发送的接口,再实现其接口,完成邮件发送Service层的代码。这个顺序我想大家没什么反对意见吧。
首先咱们先定义一个消息发送接口,它是邮件发送与短信发送的上层接口。
/** * 功能: 系统消息发送服务 <p> * 用法: * @version 1.0 */ public interface MessageService { /** * 根据消息模板表中的消息编号取得消息模板,填充,发送 * * @param bmtCode 消息模板表中的消息编号 * @param params 填充模板内容的参数 * @param to 消息的接收人 * @throws CheckException 模板不存在,或是发送消息出现异常 */ public void sendMessage(String bmtCode,Map params, String... to) throws CheckException; }
再来定义一下邮件发送器接口,由于我这里是通过velocity模板发送邮件的,所以如下定义了接口:
/** * 邮件发送器 * @usage */ public interface TempletEmailSender { /** * @param from 发件人邮箱 * @param to 收件人邮箱 * @param subject 邮件主题 * @param templet 模板 * @param paramMap 模板参数信息 * @throws CheckException */ public void sendEmailByTemplet(String from, String to, String subject,String templet, Map<String, Object> paramMap) throws CheckException; /** * @param from 发件人邮箱 * @param to 收件人邮箱(多个) * @param subject 邮件主题 * @param templet 模板 * @param paramMap 模板参数信息 * @throws CheckException */ public void sendEmailByTemplet(String from, String[] to, String subject,String templet, Map<String, Object> paramMap) throws CheckException; /** * @param mail 邮件对象 * @param templet 模板 * @param paramMap 模板参数信息 * @throws CheckException */ public void sendEmailByTemplet(Mail mail,String templet, Map<String, Object> paramMap) throws CheckException; }
接着实现邮件发送的接口:
/** * 邮件发送器默认实现 */ public class TempletEmailSenderImpl implements TempletEmailSender{ @Autowired @Qualifier("emailSenderImpl_commons") private EmailSender emailSender; @Override public void sendEmailByTemplet(String from, String to,String subject, String templet, Map<String, Object> paramMap) throws CheckException { sendEmailByTemplet(from, new String[]{to}, subject, templet, paramMap); } @Override public void sendEmailByTemplet(String from, String[] to, String subject, String templet, Map<String, Object> paramMap) throws CheckException { // 解析模板 String content ; try { content = VelocityParserUtil.getInstance().parseVelocityTemplate(templet, paramMap); } catch (Throwable t) { throw new CheckException(t); } emailSender.sendEmail(from, to, subject, content); }
大家看到了上面的实现里注入了EmailSender,它也是一个接口,它的实现里注入了JavaMail提供的邮件发送接口。定义了两层是为了区分有模板的发送和无模板的发送。我们来看看它是什么样的:
/** * 邮件发送器 * @usage */ public interface EmailSender { /** * 发送邮件 * @param from 发件人 * @param to 收件人 * @param subject 邮件主题 * @param mailBody 邮件内容 * @throws CheckException 参数校验失败或发送邮件失败时,抛出此异常 */ void sendEmail(String from, String to, String subject, String mailBody) throws CheckException; /** * 发送邮件 * @param from 发件人 * @param to 多个收件人 * @param subject 邮件主题 * @param mailBody 邮件内容 * @throws CheckException 参数校验失败或发送邮件失败时,抛出此异常 */ void sendEmail(String from, String[] to, String subject, String mailBody) throws CheckException; /** * 发送邮件 * @param mail 邮件 * @throws CheckException 参数校验失败或发送邮件失败时,抛出此异常 */ void sendEmail(Mail mail) throws CheckException; }
接着实现这个EmailSender接口:
/** * JAVA MAIL实现 */ public class EmailSenderImpl implements EmailSender,InitializingBean{ /** * Logger for this class */ private static final Logger logger = Logger.getLogger(EmailSenderImpl.class); @Autowired private ConfigService configService; private JavaMailSenderImpl sender; // 实际的发送实现 @Override public void sendEmail(String from, String to, String subject, String mailBody) throws CheckException { sendEmail(from, new String[]{to}, subject, mailBody); } @Override public void sendEmail(String from, String[] to, String subject, String mailBody) throws CheckException { // 构造MAIL对象 Mail mail = new Mail(); mail.setFrom(from); mail.setTo(to); mail.setSubject(subject); mail.setContent(mailBody); sendEmail(mail); } @Override public void sendEmail(Mail mail) throws CheckException { // 检查必要参数 if (mail == null ){ throw new CheckException("mail can not be null."); } if (ArrayUtils.isEmpty(mail.getTo())){ throw new CheckException("收件人不能为空"); } MimeMessageHelper helper = null; try { helper = new MimeMessageHelper(sender.createMimeMessage(), true, "UTF-8"); // 发件人 if (mail.getFrom() != null) { if (mail.getFromName() == null) { helper.setFrom(mail.getFrom()); } else { helper.setFrom(mail.getFrom(), mail.getFromName()); } } // 收件人 helper.setTo(mail.getTo()); // 抄送人 if (mail.getCc() != null) { helper.setCc(mail.getCc()); } // 密送人 if (mail.getBcc() != null) { helper.setBcc(mail.getBcc()); } // 邮件主题 helper.setSubject(mail.getSubject()); // 邮件内容 helper.setText(mail.getContent(), mail.isHtmlFormat()); // 附件 if (mail.getAttachments() != null) { for ( MailAttachment attachment : mail.getAttachments()) { helper.addAttachment(attachment.getFileName(),attachment.getFile()); } } // 发送时间 helper.setSentDate(new Date()); } catch (UnsupportedEncodingException e) { logger.error("sendEmail(Mail)", e); throw new CheckException(e) ; } catch (MessagingException e) { logger.error("sendEmail(Mail)", e); throw new CheckException(e) ; } // 发送 try { sender.send(helper.getMimeMessage()); } catch (MailException e) { logger.error("sendEmail(Mail)", e); throw new CheckException(e) ; } } @Override public void afterPropertiesSet() throws Exception { sender = new JavaMailSenderImpl(); // configService读出参数 Properties pros = new Properties(); pros.setProperty("mail.smtp.user", configService.getConfig(BasePropertyID.MAIL_SMTP_USER_ID)); pros.setProperty("mail.smtp.host", configService.getConfig(BasePropertyID.MAIL_SMTP_HOST_ID)); pros.setProperty("mail.smtp.port", configService.getConfig(BasePropertyID.MAIL_SMTP_PORT_ID)); pros.setProperty("mail.smtp.connectiontimeout", configService.getConfig(BasePropertyID.MAIL_SMTP_CONNECTIONTIMEOUT_ID)); pros.setProperty("mail.smtp.timeout", configService.getConfig(BasePropertyID.MAIL_SMTP_TIMEOUT_ID)); pros.setProperty("mail.smtp.from", configService.getConfig(BasePropertyID.MAIL_SMTP_FROM_ID)); pros.setProperty("mail.smtp.auth", configService.getConfig(BasePropertyID.MAIL_SMTP_AUTH_ID)); sender.setJavaMailProperties(pros); sender.setPassword(configService.getConfig(BasePropertyID.MAIL_SMTP_PASSWORD_ID)); } public ConfigService getConfigService() { return configService; } public void setConfigService(ConfigService configService) { this.configService = configService; }
O(∩_∩)O~大家又注意到了 这个接口实现里又注入了一个接口ConfigService 它是去读取邮件发送的相关配置信息,如上所示:
// configService读出参数
Properties pros = new Properties();
pros.setProperty("mail.smtp.user", configService.getConfig(BasePropertyID.MAIL_SMTP_USER_ID));
pros.setProperty("mail.smtp.host", configService.getConfig(BasePropertyID.MAIL_SMTP_HOST_ID));
pros.setProperty("mail.smtp.port", configService.getConfig(BasePropertyID.MAIL_SMTP_PORT_ID));
pros.setProperty("mail.smtp.connectiontimeout", configService.getConfig(BasePropertyID.MAIL_SMTP_CONNECTIONTIMEOUT_ID));
pros.setProperty("mail.smtp.timeout", configService.getConfig(BasePropertyID.MAIL_SMTP_TIMEOUT_ID));
pros.setProperty("mail.smtp.from", configService.getConfig(BasePropertyID.MAIL_SMTP_FROM_ID));
pros.setProperty("mail.smtp.auth", configService.getConfig(BasePropertyID.MAIL_SMTP_AUTH_ID));
sender.setJavaMailProperties(pros);
sender.setPassword(configService.getConfig(BasePropertyID.MAIL_SMTP_PASSWORD_ID));
而且由于涉及到参数的数据成员较多,就将他们一起封装到了Mail类:
/** * 功能: 封装邮件对象 <p> * 用法: * @version 1.0 */ public class Mail { /** * 发件人 */ private String from; /** * 发件人(显示) */ private String fromName; /** * 收件人 */ private String[] to; /** * 抄送 */ private String[] cc; /** * 秘密抄送 */ private String[] bcc; /** * 邮件主题 */ private String subject; /** * 邮件内容 */ private String content; /** * 附件 */ private MailAttachment[] attachments; /** * 是否以HTML格式发送 */ boolean isHtmlFormat = true; //getter与setter方法省略 }
好了 整个接口都实现了,其实排除了你发送邮件提供给邮件发送接口的几个参数,剩下的也就是调用java提供的邮件发送的API和一些邮件发送必备的配置信息,必不是很难懂吧。大家肯定注意到了邮件发送接口的velocity模板解析方法:
content = VelocityParserUtil.getInstance().parseVelocityTemplate(templet, paramMap);
它具体的实现如下所示:
/** * 功能:解析velocity模板 * <p> * 用法: * * @version 1.0 */ public class VelocityParserUtil { /** * Logger for this class */ private static final Logger logger = Logger.getLogger(VelocityParserUtil.class); private static VelocityParserUtil instance = new VelocityParserUtil(); private VelocityEngine engine = null; private VelocityParserUtil() { // init engine engine = new VelocityEngine(); try { engine.init(); } catch (Exception e) { logger.warn("VelocityParserUtil() - exception ignored", e); //$NON-NLS-1$ } } /** * 返回VelocityParserUtil实例 * @return */ public static VelocityParserUtil getInstance() { return instance; } /** * 解析velocity模板 * @param vtl * @param model * @return String * @throws ParseErrorException * @throws MethodInvocationException * @throws ResourceNotFoundException * @throws IOException */ public String parseVelocityTemplate(String vtl, Map model) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException, IOException { if (logger.isDebugEnabled()) { logger.debug("parseVelocityTemplate(String, Map) - start"); //$NON-NLS-1$ } VelocityContext velocityContext = new VelocityContext(model); StringWriter result = new StringWriter(); engine.evaluate(velocityContext, result, null, vtl); String returnString = result.toString(); if (logger.isDebugEnabled()) { logger.debug("parseVelocityTemplate(String, Map) - end"); //$NON-NLS-1$ } return returnString; } }
我们会在其他的Service中去调用邮件发送的接口,只需要在业务层里构造好邮件发送的接口所需参数,我们的邮件就可以发送出去了。还有一点请大家注意,我这里主要强调的是运用velocity模板发送邮件,接口所需要的参数templat大家不要误解为velocity模板的文件名,它其实velocity文件的文件流,是一个已经被读入的字符串。大家可以参考一下测试用例,大致可以明白是怎么回事了。
public class TempletEmailSenderTest extends BaseTestBaseForJUnit4{ @Autowired @Qualifier("templetEmailSenderImpl_commons") private TempletEmailSender sender; // @Test public void sendEmailByTemplet() throws CheckException{ String templet = "$!{user}提醒您付款$!{amount}元"; Map paramMap = new HashMap(); paramMap.put("user", "吴欣"); paramMap.put("amount", "99.9"); sender.sendEmailByTemplet("[email protected]", "[email protected]", "模板邮件", templet, paramMap); }
整个邮件的发送大家是否明了了呢,呵呵!
后面我们会将这个接口向上抽象,为了实现我们短信发送的实现,下一篇敬请期待哦