@

背景

《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方法中。

Spring的学习与实战(续)的更多相关文章

  1. Github点赞超多的Spring Boot学习教程+实战项目推荐!

    Github点赞接近 100k 的Spring Boot学习教程+实战项目推荐!   很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Boot 进行开发,毕竟它这 ...

  2. Spring的学习与实战

    目录 一.Spring起步 学习路线图 Spring的基础知识 什么是Spring Spring框架核心模块 SpringBoot 第一个Spring应用DEMO 编写自己的第一个SpringMVC例 ...

  3. spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战

    SpringBoot + Redis +SpringSession 缓存之实战 前言 前几天,从师兄那儿了解到EhCache是进程内的缓存框架,虽然它已经提供了集群环境下的缓存同步策略,这种同步仍然需 ...

  4. 【转】Spring.NET学习笔记——目录

    目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...

  5. Spring.NET学习

    Spring.NET学习笔记——目录(原)   目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) ...

  6. Spring MVC 学习总结(十)——Spring+Spring MVC+MyBatis框架集成(IntelliJ IDEA SSM集成)

    与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管理,Spring MVC负责请求的转发和 ...

  7. Spring.NET学习笔记——目录(原)

    目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...

  8. Spring mvc+hibernate+freemarker(实战)

    Spring mvc+hibernate+freemarker(实战) 博客分类: Spring Spring mvchibernatefreemarkerwebjava  今天我为大家做了一个 sp ...

  9. Spring Boot 2.x实战之StateMachine

    本文首发于个人网站:Spring Boot 2.x实战之StateMachine Spring StateMachine是一个状态机框架,在Spring框架项目中,开发者可以通过简单的配置就能获得一个 ...

随机推荐

  1. [每日一题2020.06.16] leetcode双周赛T3 5423 找两个和为目标值且不重叠的子数组 DP, 前缀和

    题目链接 给你一个整数数组 arr 和一个整数值 target . 请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target .可能会有多种方案,请你返回满足要求的两个子数组长度和的 ...

  2. 操作-读取excel

    xlrd 该模块主要用来读取excel 注:sheet表示的是excel的表,就是底下的工作栏 (1) 打开excel文件并获取所有sheet import xlrd # 打开Excel文件读取数据 ...

  3. ESP8266服务器模式 发送数据和接收数据 模板1

    功能如下: 1.将客户端发来的数据转发到串口:2.串口数据转发给所有客户端3.可连接4个客户端4.可设置静态IP地址5.指示灯闪烁表示无客户端连接,灯亮代表有客户端连接 /** 功能: 1.将客户端发 ...

  4. arduino连接1602LCD方法

    arduino连接1602LCD方法 参考代码:

  5. c++无法解析的外部符号 "int const bufferSize

    无法解析的外部符号 "int const bufferSize 严重性 代码 说明 项目 文件 行 禁止显示状态错误 LNK2001 无法解析的外部符号 "int const bu ...

  6. 大文件上传、断点续传、秒传、beego、vue

    大文件上传 0.项目源码地址 源码地址 :https://github.com/zhuchangwu/large-file-upload 它是个demo,仅供参考 前端基于 vue-simple-up ...

  7. java中“”==“” equals hashcode的关系

    ava中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比 ...

  8. MySQL一招入门

    连接mysql数据库命令:mysql -u root -p 创建mysql数据库:create databaase xx库; 创建mysql表: create table db_xx表( id int ...

  9. MongoDB快速入门教程(3.1)

    3.MongoDB进阶 3.1.权限验证 以下内容适用于Mac系统用户,window系统用户请看后面文档 3.1.1.创建超级管理员用户 默认情况下连接mongodb是不需要用户名和密码的,这样不安全 ...

  10. 判断CString 字符串里面是否全部为数字

    //原理就是去除0-9的数字,判断去除数字后的字符串是否为空,如果为空,说明字符串全部都是为数字,否则得话,就不是. strOutTimeOnNum = strouttime.TrimLeft( _T ...