专栏首页智慧zhuhuix的开发专栏Spring的学习与实战(续)

Spring的学习与实战(续)

背景

  • 在上文章中我们已经实现了一个简单的用户邮箱登记的web应用,将数据保存到mysql数据库中,并利用安全框架对web页面进行保护及实现了管理员的注册登录,又通过Spring的配置属性完成了自定义的各种配置。并了解了Spring与应用的集成的基本概念,实现集成REST API服务。
  • 本文将继续深入Spring的集成应用,实现邮件发送及集成消息队列的功能。

JavaMailSender

Spring框架提供了一种使用JavaMailSender接口发送电子邮件的简单抽象方法,而Spring Boot为其提供了自动配置以及启动程序模块。

  • JavaMailSender接口具有特殊的JavaMail功能,例如MIME消息支持。
public interface JavaMailSender extends MailSender {
    MimeMessage createMimeMessage();

    MimeMessage createMimeMessage(InputStream var1) throws MailException;

    void send(MimeMessage var1) throws MailException;

    void send(MimeMessage... var1) throws MailException;

    void send(MimeMessagePreparator var1) throws MailException;

    void send(MimeMessagePreparator... var1) throws MailException;
}

Spring集成邮件发送功能

Spirng实现邮件发送功能,需要以下步骤:
1. 添加maven依赖
2. 添加Spring邮件配置
3. 创建邮件管理Bean并注入Spring应用上下文
4. 修改业务逻辑,调用邮件发送功能
1. 添加maven依赖
 <!-- pom.xml -->	
 	<dependencies>
        <!-- 邮件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
    </dependencies>
2. 添加Spring邮件配置
### application.yml
spring:
#mail配置
  mail:
    host: smtp.163.com
    username: zhuhuix@163.com
    password: 自行设置邮箱密码
    default-encoding: UTF-8
3. 创建邮件管理Bean并注入Spring应用上下文
/**
 * 邮件发送Bean
 *
 * @author zhuhuix
 * @date 2020-07-13
 */
@Service
@Component
public class MailManager {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
    // 发件人
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private JavaMailSender javaMailSender;

    /**
     * 普通文本邮件发送
     *
     * @param to      收件人
     * @param subject 主题
     * @param text    内容
     */
    public void sendSimpleMail(String to, String subject, String text) {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setFrom(this.from);
        msg.setTo(to);
        msg.setSubject(subject);
        msg.setText(text);
        try {
            this.javaMailSender.send(msg);
            logger.info(msg.toString());
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        }

    }

    /**
     * HTML邮件发送
     *
     * @param to      收件人
     * @param subject 主题
     * @param text    内容
     */
    public void sendHTMLMail(String to, String subject, String text) {
        try {
            MimeMessage msg = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(msg, true);
            mimeMessageHelper.setFrom(this.from);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(text, true);
            this.javaMailSender.send(msg);
            logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        } catch (MessagingException ex) {
            logger.error(ex.getMessage());
        }

    }

    /**
     * 发送带有附件的邮件
     * @param to 收件人
     * @param subject 主题
     * @param text 内容
     * @param filePath 附件
     */
    public void sendAttachmentMail(String to, String subject, String text, String filePath) {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            mimeMessageHelper.setFrom(from);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(text, true);
            FileSystemResource fileSystemResource = new FileSystemResource(new File(filePath));
            String fileName = fileSystemResource.getFilename();
            mimeMessageHelper.addAttachment(fileName, fileSystemResource);
            javaMailSender.send(mimeMessage);
            logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        } catch (MessagingException ex) {
            logger.error(ex.getMessage());
        }
    }

}
4. 修改业务逻辑,调用邮件发送功能
  • 业务流程图
  • 业务逻辑
/**
 * 基于SpringMVC框架开发web应用--用户服务类
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-04 增加通过jdbcTemplate处理数据
 * @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
 * @date 2020-07-10 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MailManager mailManager;

    // 返回所有的用户
    public List<User> listUsers() {
        return (List<User>) userRepository.findAll();
    }

    // 保存用户
    public User saveUser(User user) {
        boolean firstRegister = false;
        if ((user.getId() == null || user.getId().equals(0L))) {
            firstRegister = true;
        }
        // 首次保存用户成功后发送通知邮件
        if (userRepository.save(user) != null && firstRegister == true) {
            sendMail(user);
        }
        return user;
    }

    // 发送邮件
    private void sendMail(User user) {
        // 发送文本邮件
        String text = "您的邮箱信息已登记!";
        mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);

        // 发送HTML邮件
        String content = "<html>\n" +
                "<body>\n" +
                "<h3> <font color=\"red\"> " + text + "</font> </h3>\n" +
                "</body>\n" +
                "</html>";
        mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content);

        // 发送带有附件的邮件
        String attachFilePath = "c:\\csdn-logo.png";
        mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath);
    }

    // 删除用户
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    // 查找用户
    public User findUser(Long id) {
        return userRepository.findById(id).get();
    }

    // 根据名称查找用户
    public List<User> searchUser(String name) {
        return userRepository.findByName(name);
    }

}

邮件发送功能测试

  • 登记用户
  • 登录邮箱查看

Spring集成JavaMailSender实现邮件发送小结

以上我们通过JavaMailSender接口实现了文本、超文本及带有附件的邮件的发送功能。 在书写这些程序时,采用了硬编码,可能会碰到如下问题:

  • 用Java代码创建基于HTML的电子邮件内容很繁琐且容易出错。
  • UI和业务逻辑之间没有明确区分。
  • 更改电子邮件内容及重新排列UI时,需要编写Java代码,重新编译,重新部署。 解决这些问题的方法是使用模板库(例如我们已经用到的thymelea或者freemaker),当需要发送的邮件的内容变得相当复杂时,就变得非常必要,读者可自行尝试。

RabbitMQ

RabbitMQ可以说是AMQP(Advanced Message Queue,高级消息队列协议)最杰出的实现。

RabbitMQ的基本概念

概念

描述

发送者

消息的生产者,也可以是一个向交换器发布消息的客户端应用程序

接收者

消息的消费者,也可以认为是向消息队列接收消息的服务端程序

Exchange(交换器)

用来接收发送者发送的消息并将这些消息路由给服务器中的队列

Binding (绑定)

用于消息队列和交换器之间的关联

队列

用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。

Binding key

在绑定(Binding)Exchange与Queue的同时,一般会指定一个Binding key;Routing key结合Exchange可实现路由规则。

Routing key

通过指定Routing key,结合Exchange和Routing key,可以决定消息流向哪里。

  • RabbitMQ还有象Channel 信道、Virtual Host 虚拟主机、Broker 消息队列服务器实体等概念,请读者自行研究。
RabbitMQ的消息路由走向
  • RabbitMQ的消息路由走向由Exchange的类型决定;分发消息时根据Exchange类型的不同分发策略有区别,见下表:

类型

描述

Direct

如果消息的routing key与队列的binding key相同,那么消息将会路由到该队列上。

Topic

如果消息的routing key与队列binding key(可能会包含通配符)匹配,那么消息将会路由到一个或多个这样的队列上。

Fanout

不管routing key和binding key是什么,消息都将会路由到所有绑定队列上。

Headers

与Topic Exchange类似,只不过要基于消息的头信息进行路由,而不是routing key。

本文只对Direct模型进行展开处理,其他类型请读者自行研究。关于如何绑定队列到Exchange的更详细的描述,可以参考Alvaro Videla和Jason J.W. Williams编写的RabbitMQ in Action (RabbitMQ实战)。

Spring集成RabbitMQ实现异步消息处理

1. 添加maven依赖
 <!-- pom.xml rabbitmq -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
2. Spring添加RabbitMQ配置
### application.yml
spring:
#RabbitMQ配置
  rabbitmq:
    host: 192.168.0.1
    port: 5672
    username: rabbitmq
    password: rabbitmq
    virtual-host: /
    connection-timeout: 10000
    listener:
      simple:
        acknowledge-mode: auto # 自动应答
        auto-startup: true
        default-requeue-rejected: false # 不重回队列
        concurrency: 5
        max-concurrency: 20
        prefetch: 1 # 每次只处理一个信息
        retry:
          enabled: false
    template:
      exchange: web.demo
      routing-key: user.key
3. 创建RabbitMQ配置类
/**
 * rabbitmq 配置类
 *
 * @author zhuhuix
 * @date 2020-07-14
 */
@Configuration(value = "rabbitMQConfig")
public class RabbitMQConfig {

    // 获取exchange和routing-key定义
    @Value("${spring.rabbitmq.template.exchange}")
    private  String exchange;
    @Value("${spring.rabbitmq.template.routing-key}")
    private String routingKey;

    public String getExchange() {
        return exchange;
    }

    public String getRoutingKey() {
        return routingKey;
    }

    // 自定义消息转换器
    @Bean
    public MessageConverter messageConverter() {

        return new SimpleMessageConverter() {
            @Override
            protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
                Message message = super.createMessage(object, messageProperties);
                return message;
            }
        };
    }
}
4. 创建接收消息监听程序
  • 监听消息队列,收到完整消息后,调用邮件发送程序
/**
 * rabbitmq 接收器
 *
 * @author zhuhuix
 * @date 2020-07-14
 */
@Component
public class RabbitMQReceiver {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
	// 邮件集成请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
    @Autowired
    private MailManager mailManager;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 监听消息队列
    @RabbitHandler
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange("#{rabbitMQConfig.getExchange()}"),
            key = "#{rabbitMQConfig.getRoutingKey()}",
            value = @Queue("user.queue")))
    public void receiveMessage(Message message) {
        try {
            User user = (User) rabbitTemplate.getMessageConverter().fromMessage(message);
            logger.info("接收到消息:[{}]", user.toString());
            // 收到完整消息后,调用邮件发送程序,发送通知邮件
            if (user != null) {
                sendMail(user);
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }
    }

    // 发送邮件
    // 请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
    private void sendMail(User user) {
        // 发送文本邮件
        String text = "您的邮箱信息已登记!";
        mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);

        // 发送HTML邮件
        String content = "<html>\n" +
                "<body>\n" +
                "<h3> <font color=\"red\"> " + text + "</font> </h3>\n" +
                "</body>\n" +
                "</html>";
        mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content);

        // 发送带有附件的邮件
        String attachFilePath = "c:\\csdn-logo.png";
        mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath);
    }
}
5. 修改业务逻辑,实现发送消息功能
/**
 * 基于SpringMVC框架开发web应用--用户服务类
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-04 增加通过jdbcTemplate处理数据
 * @date 2020-07-07 将jdbcTemplate处理数据程序改为Spring Data JPA的处理方式
 * @date 2020-07-10 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 * @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 返回所有的用户
    public List<User> listUsers() {
        return (List<User>) userRepository.findAll();
    }

    // 保存用户
    public User saveUser(User user) {
        boolean firstRegister = false;
        if ((user.getId() == null || user.getId().equals(0L))) {
            firstRegister = true;
        }
        // 首次保存用户成功后发送消息队列实现异步发送通知邮件
        if (userRepository.save(user) != null && firstRegister == true) {
            rabbitTemplate.convertAndSend(user);
        }
        return user;
    }


    // 删除用户
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    // 查找用户
    public User findUser(Long id) {
        return userRepository.findById(id).get();
    }

    // 根据名称查找用户
    public List<User> searchUser(String name) {
        return userRepository.findByName(name);
    }

}

功能测试

Spring集成RabbitMQ实现异步消息处理小结

  • 异步消息在要通信的应用程序之间提供了一个中间层,这样能够实现更松散的耦合和更强的可扩展性。利用消息队列的这种特性我们可以很方便地实现系统应用间的解耦:
    • 用户登记成功后,向客户端返回登记成功的同时,只是向消息队列发送消息,并不等待邮件的发送事件的结果;
    • 而消息队列接收者收到消息后,对消息进行解析,并根据解析中的邮件地址,发送通知邮件。
  • Spring支持集成RabbitMQ实现异步消息,通过使用消息监听器注解@RabbitListener,消息也可以推送至消费者的bean方法中。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 用一个实例项目重新认识分布式系统

    对于分布式系统的理解不能光停留在理论上,本文旨在通过一个实际的案例来阐述分布式系统框架的基本概念,起到抛砖引玉的效果。

    智慧zhuhuix
  • 用自定义链式栈解决力扣括号匹配问题

    由于输入的字符串长度不定,并考虑自定义一个链式栈(无栈满问题,空间可扩充)进行编码实现。

    智慧zhuhuix
  • 数据结构之链式队列的代码实现及有趣应用

    本文通过编码实现链式队列类,并模拟一个有趣的应用,能够帮助我们对链式队列有更深度的理解。

    智慧zhuhuix
  • 你还在用传统的 JDBC 持久化访问吗

    这里我会采用mybatis3.2做数据库的持久化,很多小伙伴或许还停留在老师的教导下,仍然停留在使用传统的JDBC持久化访问数据层。今天,我们来聊聊面向接口编程...

    技术zhai
  • SaaS-基于JWT的API鉴权

    如果我们每个方法都去写一段代码,冗余度太高,不利于维护,那如何做使我们的代码看起来更清爽呢?我们可以将这段代码放入拦截器去实现

    cwl_java
  • TCL成拥抱互联网最积极的“大叔级企业”,或将迎来第二春

    成立于1981年的TCL,今年35岁了,可谓“大叔级企业”,其进入电视行业有30年历史、进入手机行业有17年历史,这一从惠州成长起来的本土企业是中国硬件产业的标...

    罗超频道
  • 字符串的压缩以及解压缩

    shengjk1
  • Netflix Archaius 分布式配置管理依赖构件

    archaius是Netflix公司开源项目之一,基于java的配置管理类库,主要用于多配置存储的动态获取。主要功能是对apache common config...

    高广超
  • 魔改 TypeAdapterFactory

    感慨:Retrofit2 虽好,但是,有时候总感觉 Java 这门语言还是美中不足啊!

    HelloVass
  • Spring Async的使用&MDC继承

    执行日志中可以看到sayHello函数是在任务执行器applicationTaskExecutor的线程task-1执行的,不是main线程

    十毛

扫码关注云+社区

领取腾讯云代金券