上一篇文章,我们介绍完了ActiceMQ支持的4种持久化方式和具体配置。那么接下来,我们来看一下,在日常开发中常用的利用JDBC持久化消息的方式,同时结合mysql数据库,来为大家书写一个完整的项目,旨在让大家熟悉开发是全流程,同时再一次理解JDBC持久化的方法和AcrtiveMQ的使用。为了充分融汇知识点,我们模拟实际开发,将这个案例的实现分为2个工程:
第一个工程,利用springmvc框架搭建maven web工程,同时详细讲解通过注解的方式来进行Spring整合activemq的操作。主要完成用户注册功能,并将用户填写的数据持久化。
第二个工程,搭建一个简单的Maven工程,利用spring框架整合activemq,同时详细讲解通过xml配置文件的方式来进行Spring整合activemq的操作。主要完成激活邮件发送功能。
拿着两个工程之间的联系,就是数据的关联,那么数据如何进行关联呢?那么就需要使用到消息中间件ActiveMQ的帮助,同时,数据持久化到数据库中,所有对数据的需求都直接读取数据库即可。
第一个工程:用户注册——使用ActiveMQ的JDBC方式持久化消息到mysql数据库
使用SpringMVC框架,利用注解方式整合spring和ActiveMQ。
大体流程:
将前端页面用户注册的信息,发送到后台进行处理,调用消息中间件,将用户信息进行持久化存储。
我们在发开过程中,会使用到 Mybatis、thymeleaf、lombok、fastjson框架或技术。
开发的重点流程梳理:
- 创建数据库,已备用户数据持久到指定的数据库中。
- 配置activemq.xml文件,已在上一篇文章详细介绍,这里只做总结。包括:
添加数据源配置和修改持久化策略 - 将Mysql的驱动jar包拷贝到activemq根目录下的lib目录下。
- 使用MessageProducer时 设置持久化方式 即可。
- Maven依赖
<!-- 整合ActiveMQ引入的相关依赖 -->
<!-- javax.jms.api -->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<!-- spring-jms -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!--activcemq-pool-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.3</version>
</dependency>
<!--activemq-broker-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.3</version>
<exclusions>
<!--排除低版本的Jackson-databind-->
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<!-- thymeleaf-spring5 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
<!-- 整合Mybatis的maven依赖 -->
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- pageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
- 创建数据库
drop table if exists t_activemq_user;
create table if not exists t_activemq_user(
id int primary key not null auto_increment,
name varchar(32) not null,
email varchar(32) not null,
user_id varchar(64) not null,
status int default 0 comment '0 -未激活,1 -已激活',
create_time datetime not null
);
- 创建实体类
这其中包括User和Email两个实体类
package com.golden3young.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private String email;
private String userId;
private Integer status;
private String createTime;
}
package com.golden3young.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Email {
//邮件主题
private String subject;
//收件人
private String receiver;
//邮件内容
private String content;
}
- 创建UserMapper接口
package com.golden3young.mapper;
import com.golden3young.entity.User;
public interface UserMapper {
int addUser(User user);
}
- 创建UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.golden3young.mapper.UserMapper">
<insert id="addUser" parameterType="user">
insert into t_activemq_user
(name, email, user_id, create_time)
values (#{name}, #{email}, #{userId}, NOW())
</insert>
</mapper>
- 创建UserSerivce接口
package com.golden3young.services;
import com.golden3young.entity.User;
public interface UserService {
int addUser(User user);
}
- 创建UserService实现类
package com.golden3young.services.impl;
import com.alibaba.fastjson.JSONObject;
import com.golden3young.entity.User;
import com.golden3young.mapper.UserMapper;
import com.golden3young.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
@Transactional(rollbackFor = Exception.class, //回滚机制
propagation = Propagation.REQUIRED, //传播方式
isolation = Isolation.DEFAULT) //隔离级别
public int addUser(User user) {
//随机生成一个32位用户id
String userId = UUID.randomUUID().toString().replaceAll("-","");
//为用户设置id
user.setUserId(userId);
// 发送消息到消息队列 可以异步发送 -- 创建一个线程来异步发送
// Email(接收者\邮件内容\邮件主题\发送者(在生产者里写死))
jmsTemplate.send("email", new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
Email email = new Email("激活邮件",user.getEmail(),"请点击:http://localhost:8080/activemq-springmvc/user/" + user.getUserId());
String message = JSONObject.toJSONString(email);
return session.createTextMessage(message);
}
});
//调用mapper实现数据库存储
return userMapper.addUser(user);
}
}
- 创建UserController
package com.golden3young.controller;
import com.golden3young.entity.User;
import com.golden3young.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 跳转到注册页面
* @return
*/
@RequestMapping("/toReg")
public String toReg(){
return "reg";
}
@Autowired
UserService userService;
/**
* 专门用于前端页面的注册请求的处理
*/
@RequestMapping("/reg")
public String reg(@RequestParam String name, @RequestParam String email){
User user = new User();
user.setName(name);
user.setEmail(email);
userService.addUser(user);
return "redirect:/user/toReg";
}
}
- 创建前端注册页面 reg.html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h2>注册页面</h2>
<form th:action="@{/user/reg}">
<label>用户名:</label>
<input type="text" name="name" /> <br>
<label>邮箱:</label>
<input type="email" name="email" /> <br>
<button type="submit">立即注册</button>
</form>
</body>
</html>
采用注解+配置类的方式配置整个SpringMVC项目
1. 首先创建一个MvcInit类,用于替代原本的web.xml配置文件。
package com.golden3young.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
public class MvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
//Spring-root 容器
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
//Spring-mvc容器
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
}
//DispatcherServlet 拦截的请求地址 url-pattern
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
//配置编码过滤器
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
return new Filter[] {encodingFilter};
}
}
2. 创建一个RootConfig类,用于替代原本的Spring-root.xml配置文件。
这里我们将容器进行了划分,分为父容器和子容器,父容器即springRoot容器,子容器为springMVC容器。这一部分的内容,可以关注后续的文章,我会专门介绍。
package com.golden3young.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;
/**
原本xml中的配置代码是这样的: 大家可以比对着来看:
* <context-component-scan></context-component-scan>
* <bean class="DruidDateSource"></bean>
* <bean class="SqlSessionFactoryBean"></bean>
* <bean class="MapperScannerConfigurer"></bean>
* <bean class="DataSourceTransactionManager"></bean>
*
*/
//使用@Configuration注解作为spring标签
@Configuration //<beans></beans>
@ComponentScan(basePackages = "com.golden3young", //{"com.golden3young","com.g3y"} 可以为多个 这是一个数组
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {Controller.class, RestController.class,
ControllerAdvice.class, EnableWebMvc.class})},
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {Service.class, Repository.class})}) //这里 由于没有dtd约束,没有顺序要求了
@PropertySource(value = "classpath:jdbc.properties") //加载classpath下的jdbc.properties
@MapperScan(basePackages = "com.golden3young.mapper")
@EnableTransactionManagement //开启注解事务 如果想用AsepectJ,就需要自己写注解
@Import(ActiveMQConfig.class) //xml 中的 <import> 加载其他的配置文件 或者在rootConfig的getClass的数组里进行赋值
public class RootConfig {
//配置<bean>
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Value("${mybatis.typeAliasesPackage}")
private String typeAliasesType;
/**
* 配置数据源
* @return
*/
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driverClassName);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
return ds;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(
@Qualifier("dataSource") DataSource dataSource) throws IOException {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// dataSource mapperLocations typeAliasesPackage plugins
factory.setDataSource(dataSource);
factory.setTypeAliasesPackage(this.typeAliasesType);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(this.mapperLocations);
factory.setMapperLocations(resources);
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("reasonable","true");
interceptor.setProperties(properties);
factory.setPlugins(new Interceptor[]{interceptor});
return factory;
}
@Bean
public DataSourceTransactionManager transactionManager(){
return new DataSourceTransactionManager(this.dataSource());
}
}
- 创建一个MvcConfig类,用于替代之前的spring-mvc.xml配置文件
也就是子容器。
package com.golden3young.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.Thymeleaf;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafView;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
@Configuration //<beans></beans>
@ComponentScan(basePackages = "com.etoak", //{"com.etoak","com.et"}
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {Controller.class, RestController.class,
ControllerAdvice.class, EnableWebMvc.class})},
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,
classes = {Service.class, Repository.class})}) //这里 由于没有dtd约束,没有顺序要求了
@EnableWebMvc //就是annotation-driven
public class MvcConfig implements WebMvcConfigurer {
// since jdk1.8 需要继承WebMvcConfigurerAdapter 现在已经过时
// 配置<mvc:default-servlet-handler>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// <mvc:resources location="" mapping="">
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//mapping属性
registry.addResourceHandler("/pic/**")
.addResourceLocations("file:d:/upload"); //location属性
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
/**
* 整合Thymeleaf
* 在java config方式中使用@Bean表示一个Spring Bean
* 不给@Bean的value或者name赋值,默认的bean的name(id) 就是方法名字
*/
@Bean
public SpringResourceTemplateResolver templateResolver(){
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
//prefix suffix templateMode characterEncoding cacheable
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(){
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(this.templateResolver());
return engine;
}
@Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setCharacterEncoding("UTF-8");
resolver.setTemplateEngine(this.templateEngine());
return resolver;
}
}
- 创建一个ActiveConfig类,用来配置ActiveMQ的相关内容。
package com.golden3young.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Session;
@Configuration //标识 这是一个容器
public class ActiveMQConfig {
/**
* ActiceMQConnectionFactory
* - 构造方法(name,password,brokeURL,userAsyncSend) 是否支持异步发送
*
* CachingConnectionFactory
* - 默认缓存一个session,默认缓存发送者和消费者
* - 构造方法(TargetConnectionFactory)
* - sessionCacheSize:10
*
* JmsTemplate 相当于一个数据源jdbcTemplate
* - 构造方法(ConnectionFactory实例)
* - 属性 explicitQosEnabled:true
* - 属性 deliveryMode: 持久化
*/
@Bean
public ConnectionFactory ActiveMQConnectionFactory(){
return new ActiveMQConnectionFactory(null,null,"tcp://localhost:61616");
}
@Bean
public CachingConnectionFactory cachingConnectionFactory(){
CachingConnectionFactory factory = new CachingConnectionFactory(this.ActiveMQConnectionFactory());
//设置缓存个数
factory.setSessionCacheSize(10);
return factory;
}
@Bean
public JmsTemplate jmsTemplate(){
JmsTemplate jmsTemplate = new JmsTemplate(this.cachingConnectionFactory());
jmsTemplate.setExplicitQosEnabled(true);
//持久化
jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
//客户端手动签收消息
jmsTemplate.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
return jmsTemplate;
}
}
以上代码,详细的解释地写明了ActiveMQ持久化方式的设置,同时,也引入了客户端手动签收消息的设置。
客户端手动签收消息,就是需要客户端自己在拿取到消息后,主动调用签收方法,已告知消息中间件,中间件会在接收到签收信号后,将消息消除。如果签收方只拿数据不签收,那消息会一直存储在中间件上,造成隐患。
第二个工程——基于meven普通项目,采用xml配置文件方式实现spring整合activemq,同时监听发送邮件信息。
专门书写一个邮件发送模块来监听中间件消息的变化,一旦有新的消息就执行邮件发送功能。
- Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- javax.jms-api -->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<!-- spring-jms -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.3</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.3</version>
<!-- 排除对jackson-databind的依赖 -->
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<!-- jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<!-- 邮件发送 org.apache.curator:curator-framework:2.4.2 org.apache.curator:curator-recipes:2.4.2 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
</dependency>
<!-- 简化Bean代码的lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 创建实体类
这里由于要发送邮件,需要用到数据库中用户表 有关邮件的信息,于是创建了Email类,封装需要的数据内容。
package com.golden3young.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Email {
//邮件主题
private String subject;
//收件人
private String receiver;
//邮件内容
private String content;
}
- 创建EmailService类,专门处理邮件发送业务
package com.golden3young.service;
import com.golden3young.entity.Email;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.util.concurrent.ThreadPoolExecutor;
@Service
public class EmailService {
//封装邮件消息
@Autowired
SimpleMailMessage mailMessage;
//邮件发送对象
@Autowired
JavaMailSenderImpl sender;
//线程池
@Autowired
ThreadPoolTaskExecutor executor;
public void sendEmail(Email email){
//封装一个mailMessage
mailMessage.setSubject(email.getSubject()); //主题
mailMessage.setFrom("[email protected]"); //发件人
mailMessage.setTo(email.getReceiver()); //收件人 可以多个
mailMessage.setText(email.getContent()); //邮件内容
//抄送 - 通知 知会
mailMessage.setCc("[email protected]"); //可以多个
executor.execute(new Runnable() {
@Override
public void run() {
sender.send(mailMessage);
}
});
}
}
//Service-> listenener -> msg转json Email
- 配置spring-email.xml文件
由于我们的邮件发送功能实现是基于org.apache.curator包的,需要在使用前与spring进行整合,所以需要用到spring-email.xml配置文件,但这里不过多展开关于邮件发送功能配置的详细介绍,重点在于ActiveMQ的使用。
配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
<!-- Spring提供的发送邮件的类 -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="smtp.163.com" />
<property name="username" value="[email protected]" />
<!-- 邮箱授权码,不是邮箱密码 -->
<property name="password" value="xxxxxx" />
<property name="defaultEncoding" value="UTF-8"></property>
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.timeout">10000</prop>
</props>
</property>
</bean>
<!-- 简单邮件消息:用于封装邮件消息 还有另外一种消息格式:MIMEMessage -->
<bean id="simpleMailMessage" class="org.springframework.mail.SimpleMailMessage">
</bean>
<!-- 线程池:用于发送邮件 -->
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 最少线程数量 -->
<property name="corePoolSize" value="5" />
<!-- 空闲时间 -->
<property name="keepAliveSeconds" value="30000" />
<!-- 最大线程数量 -->
<property name="maxPoolSize" value="50" />
<!-- 提供线程池使用的缓冲队列 -->
<property name="queueCapacity" value="100" />
</bean>
</beans>
- 创建消息监听器EmailListener
创建消息监听器,其目的是自动监听ActiveMQ消息中间件中消息队列的变化,一旦在中间件上消息队列有了任何改变,将会直接触发监听器,我们的业务功能,就可以放在监听器的方法中。简单的来说,在第一个项目中,我们从前端页面接收到了用户的注册信息,同时在后台根据用户信息,我们生成了邮件的发送数据,例如:主题、内容等,同时为了传输方便,我们将它封装在了Email对象中,同时生成队列封装Email,将其发送给了ActiveMQ中间件,那么这个Email对象就会被存储到ActiceMQ的消息队列中,等待消费者的使用。
作为消费者,我们当然可以在代码中直接调用消息队列中的指定数据,但是,在实际的开发中,我们无法知道用户是什么时间注册的,也无法做到,在每个用户注册完成后,运行一遍我们的代码,那是不现实的。所以,我们需要一种解决办法,那就是无论用户什么时间注册,也就是无论消息队列什么时候添加了新数据,直接触发我们邮件发送的代码,而不需要自己手动去运行,这种功能的实现,就是监听器的作用。
具体代码:
package com.golden3young.listener;
import com.alibaba.fastjson.JSONObject;
import com.golden3young.entity.Email;
import com.golden3young.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* 实现 自定义的 邮件监听器
*/
public class EmailListener implements MessageListener{
@Autowired
EmailService service;
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage){
try {
String msg = ((TextMessage) message).getText().toString();
System.out.println("收到队列消息:" + msg);
Email email = JSONObject.parseObject(msg,Email.class);
service.sendEmail(email);
//签收消息
message.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
这种消息监听器是ActiveMQ为我们提供的,我们在自定义监听器的时候,需要实现MessageListener接口,覆盖onMessage方法,将我们具体的操作,写在这个方法中即可。这个方法为我们提供了一个参数,那就是新增的消息对象,也就是说,中间件每新增一个消息,就会触发所有实现了MessageListener接口的监听器的onMessage方法。
那么,在实际开发过程中,消息中间件中缓存的消息是多种多样的,我们需要有针对性的进行消息的处理,就比如我们的案例中,需要对有关邮箱的消息进行处理,其他类型的消息我们在这个工程中,并不需要。
我们需要对它进行类型的判断和内容的判断,所谓类型的判断,就是我们得确保拿到的这个队列消息是我们想要的内容,比如就是Email对象,那么如何确保呢?就是这个队列的名字和组名来指定,这个操作不是在这里完成的,而是在配置文件中,下文可以看到。其实这里一旦触发了监听器,就证明是我们指定的消息来了,如果是其他的消息,这个监听器压根就不会触发,这样就有了精准的监听,而不会所有的队列消息都来执行一遍这个监听器,那是有问题的;其次,就是对内容的判断,由于我们在发送前,是将Email对象封装成了JSON格式的数据,所以在发送途中,是字符串,在ActiceMQ中对应的类型是TextMessage类型,所以,如果新增的数据即使队列名和组名对应上了我们的监听器,我们也得对它进行第二道判断,就是它是不是这个类型的数据,如果不是,我们后续的代码中无法将它转成Email类型的对象,那也就没有办法将代码继续下去,所以这一步也非常的关键。
那么,我们的代码,在一开始对当前新增的message对象进行了if判断,如果它是TextMessage类型的,我们才继续对它进行后续操作,如果不是,那我们也没有什么需要对它进行处理的,直接return返回调用的地方即可。我们在ActiveMQ中的传递的消息是基于JSON格式的,所以接收到消息后,我们需要对它进行解析。直接翻译成Email对象,方便我们的操作,也是OO思想的体现。
在这个方法中,我们拿到了Email对象,需要做的就是,调用邮件发送的相关代码,将这个Email对象传递过去,让那个方法按照Email对象中的数据进行发送即可。至于发送到哪里,怎么发,那些都是刚才在EmailService类和spring-email.xml文件中配置好了的。
注意: 代码中注释了一句 手动签收消息的代码,这是由于我们在activemq的配置文件中将监听器的签收方式设定成了手动签收(下一段代码就会看到)。关于手动签收和自动签收的解释在前面也已经解释过了。需要再次提醒大家的是,一定在处理完消息后,将消息进行手动的签收,也就是message.acknowledge();代码,来告知中间件可以将消息销毁了,否则,消息即使被我们使用了,中间件的仓库里还是有这条消息记录的。
- 使用xml文件整合spring-activemq
代码部分已经结束了,最后就是重头戏,也是本文的重点之一,使用xml文件的配置方式,利用spring整合activemq:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
">
<context:component-scan base-package="com.golden3young" />
<!--引入email配置文件-->
<import resource="classpath:spring-email.xml"/>
<!--
ActiveMQConnectionFactory
CachingConnectionFactory
队列
监听器
DefaultMessageListenerContainer 连接队列和监听器的容器
-->
<bean id="mqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<constructor-arg name="userName" value="null" />
<constructor-arg name="password" value="null" />
<constructor-arg name="brokerURL" value="tcp://localhost:61616" />
<!--异步发送-->
<property name="useAsyncSend" value="true" />
</bean>
<bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg name="targetConnectionFactory" ref="mqConnectionFactory" />
<property name="sessionCacheSize" value="10" />
</bean>
<!--消息队列-->
<bean id="emailQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!--队列名称-->
<constructor-arg name="name" value="email" />
</bean>
<!--消息监听器:自定义的监听器-->
<bean id="emailListener" class="com.golden3young.listener.EmailListener" />
<!--监听器容器-->
<bean id="container" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="cachingConnectionFactory" />
<property name="destination" ref="emailQueue"/>
<!--签收方式:1 自动 2 手动-->
<property name="sessionAcknowledgeMode" value="2"/>
<property name="messageListener" ref="emailListener"/>
</bean>
</beans>
以上代码中,已经对重点内容进行了注解的添加,相信大家可以理解。我们在这里,总结一下,关于Spring整合ActivceMQ的配置文件如何书写?或者说都需要配置哪些内容?
- ActiveMQConnectionFactory
- CachingConnectionFactory
- 消息队列
- 监听器
- DefaultMessageListenerContainer 连接队列和监听器的容器
在这里,我对以上的配置内容进行一下解释,说明一下,为什么要配置这些内容,如果不配置可不可以。
- ActiveMQConnectionFactory和CachingConnectionFactory
ActiveMQConnectionFactory是ActiveMQ原生的连接工程, 默认的maxThreadPoolSize=1000,也就是每个connection的session线程池最大值为1000,可以根据自己应用定制。
我们一般不直接用这个连接工厂,原因是:这个connectionFactory不会复用connection、session、producer、consumer,每次连接都需要重新创建connection,再创建session,然后调用session的创建新的producer或者consumer的方法,然后用完之后依次关闭,比较浪费资源。
我们一般用这个连接工厂作为其他拥有更高级功能(缓存)的连接工厂的参数。也就是对这个连接工厂进行改装,把他作为核心,然后开始加一个增强罩。
那么CachingConnectionFactory,就是我们的增强罩。
CachingConnectionFactory继承了SingleConnectionFactory(仅有一个Connection),所以它拥有SingleConnectionFactory的所有功能,同时它还新增了缓存功能,它可以缓存Session、MessageProducer和MessageConsumer。是spring2.5.3之后推出的首选方案。
默认情况下,cachingConnectionFactory默认只缓存一个session,针对低并发足够。sessionCacheSize =1. 默认缓存producer、consumer。
这里给它们id,是因为下面的配置中需要引用到它。
-
消息队列
这里就是上文提到的,监听器如何来配置监听指定的队列的消息,就是通过这里。指定消息队列的类型和id,然后通过属性的赋值来指定是什么类型的队列。这里给它id,是因为下面的配置中需要引用到它。
-
消息监听器:自定义的监听器
由于我们现在所有的类和对象都交由Spring管理并创建,想要被Spring使用,就需要在配置文件中进行配置,那么我们自定义的监听器,如果要被Spring进行管理的话,就需要在这里进行声明。Class指向的就是我们自定义监听器的位置。这里给它id,是因为下面的配置中需要引用到它。 -
监听器容器
最后这个监听器容器,才是配置的核心,我们邮件发送功能的核心,就是监听器的实现。那么这个监听器想要正常被Spring运行,就需要对它进行声明。这个监听器容器,就是一个声明。它是整个监听器所有部门的合成品,前面4个组件的配置,其实就是为了给它使用。其实在加载监听器容器的时候,加载的是这个Bean对象,但是单纯凭这个容器空壳是没有作用的,它当中需要填满具体的功能实现的零件,也就是上面的那些配置:connectionFactory,destination,messageListener,这些配置其实都是它的属性,当然它还有其他的一些属性,比如sessionAcknowledgeMode,用来指定签收的方式等等。所以,可以看出来,在property属性的配置时,有value赋值、有ref引用,凡是ref引用,就是非先单独声明出零件,然后在这里进行组装。