【案例演示】基于SpringMVC框架+注解配置+XML文件配置的方式,利用ActiveMQ具体实现用户注册后发送Email激活账号功能

上一篇文章,我们介绍完了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框架或技术。

开发的重点流程梳理:

  1. 创建数据库,已备用户数据持久到指定的数据库中。
  2. 配置activemq.xml文件,已在上一篇文章详细介绍,这里只做总结。包括:
    添加数据源配置修改持久化策略
  3. 将Mysql的驱动jar包拷贝到activemq根目录下的lib目录下。
  4. 使用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的配置文件如何书写?或者说都需要配置哪些内容?

  1. ActiveMQConnectionFactory
  2. CachingConnectionFactory
  3. 消息队列
  4. 监听器
  5. DefaultMessageListenerContainer 连接队列和监听器的容器

在这里,我对以上的配置内容进行一下解释,说明一下,为什么要配置这些内容,如果不配置可不可以。

  • ActiveMQConnectionFactoryCachingConnectionFactory

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引用,就是非先单独声明出零件,然后在这里进行组装。

发布了10 篇原创文章 · 获赞 1 · 访问量 386

猜你喜欢

转载自blog.csdn.net/m0_46193073/article/details/104025108