Spring mail 邮件服务及其参数配置(properties文件的读写)

来源:互联网 发布:库存软件手机版 编辑:程序博客网 时间:2024/05/30 05:17

一个Web 系统通常会少不了邮件服务的,比如用于注册,密码找回,订单提醒等应用场景。Spring 封装了一个简单易用的关于邮件发送的工具类JavaMailSenderImpl 。

系统要提供邮件服务,那得需要一个邮件服务器,用于发送和回复邮件。如果有条件专门弄一个邮件服务器那固然是最好的,但是也可以简单的使用163或者qq提供的邮件服务。

例如注册了一个example@163.com的邮箱账号,在 设置 勾选 POP3/SMTP服务,然后保存。点击左侧导航栏中的 客户端授权密码 ,开启客户端授权码,重置授权码,你会收到一个授权码的短信,这个授权码就是用来第三方客户端登录的密码,这个待会会使用到。

1.核心工具类MailUtil.java

import java.util.Date;import java.util.Properties;import javax.mail.MessagingException;import javax.mail.internet.MimeMessage;import org.springframework.core.io.Resource;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSenderImpl;import org.springframework.mail.javamail.MimeMessageHelper;public class MailUtil {    public static JavaMailSenderImpl mailSender = createMailSender(ConfigInfo.mail_host,ConfigInfo.mail_port,ConfigInfo.mail_username,            ConfigInfo.mail_password,ConfigInfo.mail_smtp_timeout);    public static String mailFrom = ConfigInfo.mail_from;    private static JavaMailSenderImpl createMailSender(String host,int port,String username,String password,int timeout){        JavaMailSenderImpl sender = new JavaMailSenderImpl();        sender.setHost(host);        sender.setPort(port);        sender.setUsername(username);        sender.setPassword(password);        sender.setDefaultEncoding("Utf-8");        Properties p = new Properties();        p.setProperty("mail.smtp.timeout",timeout+"");        p.setProperty("mail.smtp.auth","true");        sender.setJavaMailProperties(p);        return sender;    }    //发送测试的邮件    public static void sendMailForTest(String host,int port,String username,String password,String from,            String to){        SimpleMailMessage mail = new SimpleMailMessage();        mail.setFrom(from);        mail.setTo(to);        mail.setSubject("这是测试邮件,请勿回复!");        mail.setSentDate(new Date());// 邮件发送时间        mail.setText("这是一封测试邮件。如果您已收到此邮件,说明您的邮件服务器已设置成功。请勿回复,请勿回复,请勿回复,重要的事说三遍!");        JavaMailSenderImpl sender = createMailSender(host,port,username,password,25000);        sender.send(mail);    }    public static void sendTextMail(String to,String subject,String text){        SimpleMailMessage mail = new SimpleMailMessage();        mail.setFrom(mailFrom);        mail.setTo(to);        mail.setSubject(subject);        mail.setSentDate(new Date());// 邮件发送时间        mail.setText(text);        mailSender.send(mail);    }    public static void sendHtmlMail(String to,String subject,String html) throws MessagingException {        MimeMessage mimeMessage = mailSender.createMimeMessage();        // 设置utf-8或GBK编码,否则邮件会有乱码        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");        messageHelper.setFrom(mailFrom);        messageHelper.setTo(to);        messageHelper.setSubject(subject);        messageHelper.setText(html, true);        mailSender.send(mimeMessage);    }    public static void sendFileMail(String to,String subject,String html,String contentId,Resource resource) throws MessagingException {        MimeMessage mimeMessage = mailSender.createMimeMessage();        // 设置utf-8或GBK编码,否则邮件会有乱码        MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");        messageHelper.setFrom(mailFrom);        messageHelper.setTo(to);        messageHelper.setSubject(subject);        messageHelper.setText(html, true);        //FileSystemResource img = new FileSystemResource(new File("c:/350.jpg"));        messageHelper.addInline(contentId, resource);        // 发送        mailSender.send(mimeMessage);    }}

为了使用方便,采用静态方法的实现方式,其中的JavaMailSenderImpl 实例是通过代码的方式创建的,脱离了Spring容器的管理。当然也可以使用Spring注入的方式:

<bean id="propertyConfigurer"          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">          <property name="location" value="classpath:config.properties" />      </bean> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">        <property name="host" value="smtp.163.com" />        <property name="port" value="25" />        <property name="username" value="${mail_username}"        <property name="password" value="${mail_password}" />        <property name="defaultEncoding" value="UTF-8"></property>          <property name="javaMailProperties">            <props>                <prop key="mail.smtp.auth">true</prop>                <prop key="mail.smtp.timeout">25000</prop>            </props>        </property>    </bean>

在代码中直接这样注入:

//1)首先类需要用 @Component 注解类,并要配置扫描此包,让Spring识别到。//2)然后JavaMailSender实例通过@Autowired来自动装配,其他不变//3)需要在Spring容器中配置属性文件PropertyPlaceholderConfigurerpublic static JavaMailSender mailSender;@Autowiredpublic void setMailSender(JavaMailSender mailSender){    MailUtil.mailSender = mailSender;}

2.config.properties

mail_host=smtp.163.commail_port=25mail_username=example@163.commail_password=NiDongDeShouQuanMamail_smtp_timeout=25000mail_from=example@163.com

3.ConfigInfo.java (与上面的属性文件对应)

package com.jykj.demo.util;import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;import java.util.Properties;public class ConfigInfo {    public static final String PROPERTIES_DEFAULT = "config.properties";//类路径下的属性文件名    //mail    public static String mail_host;    public static int mail_port;    public static String mail_from;    public static String mail_username;    public static String mail_password;    public static int mail_smtp_timeout;    static{        initOrRefresh();    }    //初始化或更新缓存    public static void initOrRefresh(){        Properties p=new Properties();        try {            InputStream in = ConfigInfo.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE_PATH);            p.load(in);            in.close();            mail_host = p.getProperty("mail_host","smtp.163.com");            mail_port = Integer.parseInt(p.getProperty("mail_port","25"));            mail_from = p.getProperty("mail_from");            mail_username = p.getProperty("mail_username");            mail_password = p.getProperty("mail_password");            mail_smtp_timeout = Integer.parseInt(p.getProperty("mail_smtp_timeout","25000"));        } catch (Exception e) {            e.printStackTrace();        }    }}

现在有这样个需求:需要一个参数配置界面来配置邮件服务器,里面的参数如主机、端口,账号等等都可以更改的。为了实现它,需要对config.properties文件进行读写操作,这样就不能用Spring注入JavaMailSender 实例的方式,因为Spring容器在初始化时只会加载.properties文件一次,运行时修改了属性文件,需要重启应用才能生效,这显然是不合理的,不能重启应用所以只能采用java的对properties操作的API,把它看成普通文件进行读写操作,更改完后重新加载属性文件即可,这样让config.properties脱离Spring容器管理。

mail设置

邮件服务登录密码 就是之前提到的 授权码。
发送测试邮件 : 将通过发送账号example@163.com 向 another@qq.com发送一封测试的邮件,收到了则表示该配置成功。

接下来实现保存配置参数的功能,这主要是对属性文件的读写操作。

4.ConfigUtil.java(属性文件的读写)

import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.URISyntaxException;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.Map.Entry;import java.util.Properties;import java.util.Set;//属性文件 的读写public class ConfigUtil {       public static String getProperty(String key) throws IOException {        return getProperty(ConfigInfo.PROPERTIES_DEFAULT,key);    }    public static Object setProperty(String propertyName,String propertyValue) throws URISyntaxException, IOException {        return setProperty(ConfigInfo.PROPERTIES_DEFAULT,propertyName,propertyValue);    }    public static void setProperties(Set<Entry<String, Object>> data) throws IOException, URISyntaxException{        setProperties(ConfigInfo.PROPERTIES_DEFAULT,data);    }    // 读取Properties的全部信息    public static Map<String,String> getAllProperties() throws IOException {        Properties pps = new Properties();        InputStream in =ConfigUtil.class.getClassLoader().getResourceAsStream(ConfigInfo.PROPERTIES_DEFAULT);        pps.load(in);        in.close();        Enumeration<?> en = pps.propertyNames(); // 得到配置文件的名字        Map<String,String> map = new HashMap<String,String>();        while (en.hasMoreElements()) {            String strKey =  en.nextElement().toString();            map.put(strKey,pps.getProperty(strKey));        }        return map;    }    public static String getProperty(String filePath,String key) throws IOException {        Properties pps = new Properties();        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        pps.load(in);        in.close();        return pps.getProperty(key);    }    public static Object setProperty(String filePath,String propertyName,String propertyValue) throws URISyntaxException, IOException {        Properties p=new Properties();        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        p.load(in);//        in.close();        Object o = p.setProperty(propertyName,propertyValue);//设置属性值,如属性不存在新建        OutputStream out=new FileOutputStream(new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//输出流        p.store(out,"modify");//设置属性头,如不想设置,请把后面一个用""替换掉        out.flush();//清空缓存,写入磁盘        out.close();//关闭输出流        ConfigInfo.initOrRefresh();//刷新缓存        return o;    }    public static void setProperties(String filePath,Set<Entry<String, Object>> data) throws IOException, URISyntaxException{        Properties p=new Properties();        InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        p.load(in);//        in.close();        for ( Entry<String,Object> entry : data) { //先遍历整个 people 对象              p.setProperty( entry.getKey(),entry.getValue().toString());//设置属性值,如属性不存在新建        }          OutputStream out=new FileOutputStream(new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//输出流        p.store(out,"modify");//设置属性头,如不想设置,请把后面一个用""替换掉        out.flush();//清空缓存,写入磁盘        out.close();//关闭输出流        ConfigInfo.initOrRefresh();//刷新缓存    }}

5.前端页面(使用的是Freemarker模板)

<form id="formParams" action="post">                <table class="table table-bordered table-striped">                    <thead><tr><th>参数名</th><th>参数值</th><th>说明</th></tr></thead>                    <tbody>                        <tr><td colspan="3" align="center"><span style="font-weight: bolder;">邮件服务</span></td></tr>                        <tr>                            <td>邮件服务器主机(SMTP)</td>                            <td><input type="text" name="mail_host" value="${mail_host!'smtp.163.com'}" style="width:100%;"/></td>                            <td>邮件服务器主机host,目前只支持SMTP协议(可以是163或者qq)</td>                        </tr>                        <tr>                            <td>邮件服务器端口</td>                            <td><input type="number" name="mail_port" value="${mail_port!'25'}" style="width:100%;"/></td>                            <td>邮件服务器端口</td>                        </tr>                        <tr>                            <td>邮件服务登录账号</td>                            <td><input type="email" name="mail_username" value="${mail_username!'username@163.com'}" style="width:100%;"/></td>                            <td>登录邮件服务器的账号,例如username@163.com</td>                        </tr>                        <tr>                            <td>邮件服务登录密码</td>                            <td><input type="password" name="mail_password" value="${mail_password!'234'}" style="width:100%;"/></td>                            <td>登录邮件服务器的密码,该密码通常是通过短信动态授权第三方登录的密码</td>                        </tr>                        <tr>                            <td>连接服务器超时(毫秒)</td>                            <td><input type="number" name="mail_smtp_timeout" value="${mail_smtp_timeout!'25000'}" style="width:100%;"/></td>                            <td>使用账号密码登录邮件服务器连接超时(毫秒)</td>                        </tr>                        <tr>                            <td>邮件的发送账号</td>                            <td><input type="email" name="mail_from" value="${mail_from!'username@163.com'}" style="width:100%;"/></td>                            <td>邮件的发送账号,用于系统发送邮件的账号,例如username@163.com</td>                        </tr>                        <tr>                            <td>发送测试邮件账号,看配置是否正确</td>                            <td><input type="email" id="mailTo" placeholder="example@163.com" style="width:100%;"/></td>                            <td><button type="button" class="btn btn-primary" onclick="sendTestMail()">发送测试邮件</button></td>                        </tr>                        <tr><td colspan="3" align="center">                            <button type="button" class="btn btn-primary" onclick="saveParams()">保存</button>                            <button type="button" class="btn btn-primary" onclick="$('#formParams')[0].reset()">重置</button>                        </td></tr>                    </tbody>                </table>                </form>
<script>var regMail = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/;/* 功能 */function saveParams(){    if(confirm("更改参数设置很有可能会导致系统功能异常(如果出现问题,请联系管理员),确定要保存更改吗?"))    {        var from = $('#formParams input[name=mail_from]').val();        var username = $('#formParams input[name=mail_username]').val();        if(!regMail.test(from) || !regMail.test(username)){            alert('邮箱格式不正确,请输入有效的邮件账号!');            return ;        }        var data = $('#formParams').serializeArray();        var obj=new Object();         //将array转换成JSONObject        $.each(data,function(index,param){              if(!(param.name in obj)){                  obj[param.name]=param.value;              }          });          $.ajax({            type: "POST",            url: "params_modify.do",            contentType: "application/json; charset=utf-8",            data: JSON.stringify(obj),            dataType: "json",            success: function (message) {                alert(message.info);            },            error: function (message) {                alert(message);            }        });    }}function sendTestMail(){    var to = $('#mailTo').val();    var from = $('#formParams input[name=mail_from]').val();    var username = $('#formParams input[name=mail_username]').val();    var password = $('#formParams input[name=mail_password]').val();    var host  = $('#formParams input[name=mail_host]').val();    var port  = $('#formParams input[name=mail_port]').val();    if(!regMail.test(to) || !regMail.test(from) || !regMail.test(username)){        alert('邮箱格式不正确,请输入有效的邮件账号!');        return ;    }    var p = {mail_host:host,mail_port:port,mail_username:username,mail_password:password,mail_from:from,mail_to:to};    $.post("params_sendTestMail.do",p,function(data){        data = eval('('+data+')');        alert(data.info);    });}</script>

采用的是ajax提交json的方式,注意dataType要设置为json,即提交的json内容类似于 {mail_username:xxx,mail_password:xxx……}

6. 控制器Controller的写法

@RequestMapping(value = "/params_modify.do", produces="text/html;charset=utf-8",method=RequestMethod.POST)    @ResponseBody    public String params_modify(@RequestBody String data){        try {            JSONObject jo = JSONObject.parseObject(data);            ConfigUtil.setProperties(jo.entrySet());            return JSON.toJSONString(new Result(true,"参数设置成功!"));        } catch (IOException e) {            e.printStackTrace();            return JSON.toJSONString(new Result(false,"出现IO异常:可能配置文件找不到"));        } catch (URISyntaxException e) {            e.printStackTrace();            return JSON.toJSONString(new Result(false,"出现URISyntax异常:可能配置文件不对"));        }    }    @RequestMapping(value = "/params_sendTestMail.do", produces="text/html;charset=utf-8",method=RequestMethod.POST)    @ResponseBody    public String params_sendTestMail(String mail_host,int mail_port,String mail_username,String mail_password,                    String mail_from,String mail_to){        try{            MailUtil.sendMailForTest(mail_host,mail_port,mail_username,mail_password,mail_from,mail_to);            return JSON.toJSONString(new Result(true,"测试邮件发送成功,请注意查收!"));        }catch (MailAuthenticationException e) {            e.printStackTrace();            return JSON.toJSONString(new Result(false,"邮件认证异常:authentication failure(认证失败)"));        }catch(MailSendException e){            e.printStackTrace();            return JSON.toJSONString(new Result(false,"邮件发送异常:failure when sending the message(发送消息失败)"));        }catch(MailParseException  e){            e.printStackTrace();            return JSON.toJSONString(new Result(false,"邮件消息解析异常:failure when parsing the message(消息解析失败)"));        }    }

控制器通过@RequestBody 来接收前端提交的json格式的字符串数据。
通过修改properties属性文件,更新完成后让ConfigInfo调用它的initOrRefresh()方法重新读取一次配置文件,这样就不必重启应用了,注意需要将tomcat的server.xml中的reloadable设置成false,否则更改类路径下的properties文件后tomcat会重新启动该应用
<Context docBase="HNDYX" path="/demo" reloadable="false" source="org.eclipse.jst.jee.server:HNDYX"/></Host>

总结:上面用到的东西还是蛮多的,其中属性文件的读写可能要花费一点时间去理解。Spring封装的邮件API使用起来非常简单。在实际应用中,系统参数配置修改还是常见的需求,一般这种键值对的配置用一个属性文件保存即可,通过修改属性文件达到修改参数配置的目的,对于不需要更改的只读的属性文件,例如jdbc.properties,那使用Spring容器来管理加载一次即可,这些数据库的连接等信息并不需要动态更改,如果真的需要动态切换数据库,那么可以参考上面提供的一种思路。
另外当然可以采用数据库存储的方式来实现,单独建立一张键值对的表,配置参数的修改则转换为了对普通表的增删改。

最后在本地测试,需要你的计算机安装SMTP服务,控制面板-》添加功能-》添加 SMTP服务,并把它开启。

7.读写WEB-INF目录下的属性文件

修改class路径下的属性文件会导致tomcat重启服务,这并不是我们所期望的。所以将属性文件放在其他目录下例如直接放在WEB-INF目录下(与web.xml一样),这样在修改属性文件后并不会导致Tomcat重启。所以下面需要修改下代码 ConfigUtil.java:

import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.URISyntaxException;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.Map.Entry;import java.util.Properties;import java.util.Set;//WEB-INF 目录下的属性文件 的读写  public class ConfigUtil {    public static String getProperty(String key) throws IOException {        return getProperty(ConfigInfo.PROPERTIES_DEFAULT,key);    }    public static String getProperty(String filePath,String key) throws IOException {        Properties pps = new Properties();        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        String path = getPathForWebinf();        InputStream in = new FileInputStream(path+filePath);        pps.load(in);        in.close();        return pps.getProperty(key);    }    public static Object setProperty(String propertyName,String propertyValue) throws URISyntaxException, IOException {        return setProperty(ConfigInfo.PROPERTIES_DEFAULT,propertyName,propertyValue);    }    public static void setProperties(Set<Entry<String, Object>> data) throws IOException, URISyntaxException{        setProperties(ConfigInfo.PROPERTIES_DEFAULT,data);    }    // 读取Properties的全部信息    public static Map<String,String> getAllProperties() throws IOException {        Properties pps = new Properties();        String path = getPathForWebinf();        InputStream in = new FileInputStream(path+ConfigInfo.PROPERTIES_DEFAULT);        //InputStream in =ConfigUtil.class.getClassLoader().getResourceAsStream(ConfigInfo.PROPERTIES_DEFAULT);        pps.load(in);        in.close();        Enumeration<?> en = pps.propertyNames();        Map<String,String> map = new HashMap<String,String>();        while (en.hasMoreElements()) {            String strKey =  en.nextElement().toString();            map.put(strKey,pps.getProperty(strKey));        }        return map;    }    public static Object setProperty(String filePath,String propertyName,String propertyValue) throws URISyntaxException, IOException {        Properties p=new Properties();        String path = getPathForWebinf();        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        InputStream in = new FileInputStream(path+filePath);        p.load(in);//        in.close();        Object o = p.setProperty(propertyName,propertyValue);//设置属性值,如属性不存在新建        OutputStream out=new FileOutputStream(path+filePath);        p.store(out,"modify");//设置属性头,如不想设置,请把后面一个用""替换掉        out.flush();//清空缓存,写入磁盘        out.close();//关闭输出流        ConfigInfo.initOrRefresh();//刷新缓存        return o;    }    public static void setProperties(String filePath,Set<Entry<String, Object>> data) throws IOException, URISyntaxException{        Properties p=new Properties();        String path = getPathForWebinf();        InputStream in = new FileInputStream(path+filePath);        //InputStream in = ConfigUtil.class.getClassLoader().getResourceAsStream(filePath);        p.load(in);//        in.close();        for ( Entry<String,Object> entry : data) { //先遍历整个 people 对象              p.setProperty( entry.getKey(),entry.getValue().toString());//设置属性值,如属性不存在新建        }          OutputStream out=new FileOutputStream(path+filePath);        //new File(ConfigUtil.class.getClassLoader().getResource(ConfigInfo.PROPERTIES_DEFAULT).toURI()));//输出流        p.store(out,"modify");//设置属性头,如不想设置,请把后面一个用""替换掉        out.flush();//清空缓存,写入磁盘        out.close();//关闭输出流        ConfigInfo.initOrRefresh();//刷新缓存    }    //获取WEB-INF路径    public static String getPathForWebinf(){        String path = ConfigUtil.class.getResource("/").getPath();//得到工程名WEB-INF/classes/路径        path=path.substring(1, path.indexOf("classes"));//从路径字符串中取出工程路径        return path;    }}

另外需要改ConfigInfo.java文件中的一个地方:
class路径的InputStream 改成 WEB-INF路径的InputStream

InputStream in = new FileInputStream(ConfigUtil.getPathForWebinf()+PROPERTIES_DEFAULT);            //InputStream in = ConfigInfo.class.getClassLoader().getResourceAsStream(PROPERTIES_DEFAULT);

8.Controller中读取属性文件

例如:

@Controllerpublic class SimpleController {    @Autowired    private ResourceLoader resourceLoader;    @RequestMapping("/WEB-INF-file")    @ResponseBody    public String testProperties() throws IOException {        String content = IOUtils.toString(resourceLoader.getResource("/WEB-INF/target_file.txt").getInputStream());        return "the content of resources:" + content;    }}

请参考 Read file under WEB-INF directory example

拓展阅读:270.道德经 第六十章3
非其神不伤人也,圣人亦弗伤也。夫两不相伤,故德交归焉。
译:不是灵验了就不伤人,是因为有道的领导者也不伤人。这两个都不伤害老百姓,德行都让老百姓收益了。
即便是有鬼神之力,最终也是被人的意志影响,或帮助,或伤害,取决于人自身的行为。

0 0