zoukankan      html  css  js  c++  java
  • 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>
    " +
                    "<body>
    " +
                    "<h3> <font color="red"> " + text + "</font> </h3>
    " +
                    "</body>
    " +
                    "</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>
    " +
                    "<body>
    " +
                    "<h3> <font color="red"> " + text + "</font> </h3>
    " +
                    "</body>
    " +
                    "</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方法中。
  • 相关阅读:
    转载-解决ORACLE 在控制台进行exp,导出时,空表不能导出
    Oracle数据库创建用户与数据库备份小结
    C#语言-NPOI.dll导入Excel功能的实现
    DataTable转泛型List
    算法学习 之 欧几里得算法和扩展欧几里得算法(三 完)
    算法学习 之 欧几里得算法和扩展欧几里得算法(二)
    算法学习 之 欧几里得算法和扩展欧几里得算法(一)
    SWUST OJ Gold Nuggets Distribution(0490)
    SWUST OJ NBA Finals(0649)
    SWUST OJ 青蛙的约会之二(0481)
  • 原文地址:https://www.cnblogs.com/zhuhuix/p/13298965.html
Copyright © 2011-2022 走看看