@

背景

《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. ESP8266服务器模式 发送数据和接收数据 模板1

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

  2. 2、Redis如何配置成一个windows服务并且设置一键安装卸载与启停

    每天启动redis虽然只是一个命令行的事情,但是还是比较烦,所以…… 参考文档:Windows Service Documentation.docx 默认前提:Redis已安装并配置完成(不知道如何配 ...

  3. psp表格

    陈康杰psp表格 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 10 10 Estimate 估计这个任务 ...

  4. python django 批量上传文件并绑定对应文件的描述

  5. Java中的四种引用方式

      无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与"引用"有关.在Java语言中,将引用又分为强引用.软引用.弱引用 ...

  6. java基础 内部类详解

    什么是内部类? 1.内部类也是一个类: 2.内部类位于其他类声明内部. 内部类的常见类型 1.成员内部类 2.局部内部类 3.匿名内部类 4.静态内部类 简单示例 /** * 外部类 * */ pub ...

  7. ubuntu上面安装mysql

    一.安装mysql 1. 安装需要使用root账号,如果不会设置root账号的请自行google.安装mysql过程中,需要设置mysql的root账号的密码,不要忽略了. sudo apt-get ...

  8. SpringMVC中Map、Model、ModelMap、ModelAndView之间的关系及区别

    首先,在了解这三者之前,需要知道一点:SpringMVC在调用方法前会创建一个隐含的数据模型(Model),作为模型数据的存储容器, 成为”隐含模型”. 如果controller方法的参数为Moedl ...

  9. Flink1.10全文跟读翻译

    前言 突然的一个想法,我想把flink官网英语版全部看一遍翻译出来,并且带上自己的理解.自己不是什么大神,只是想这样做一遍,有人说不是有中文版,因为我自己想练习一下英语和对flink的理解吧!工作是一 ...

  10. Python 简明教程 --- 13,Python 集合

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 如果代码和注释不一致,那很可能两者都错了. -- Norm Schryer 目录 前几节我们已经介绍 ...