上一篇文章,我們介紹完了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引用,就是非先單獨聲明出零件,然後在這裏進行組裝。