【案例演示】基於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引用,就是非先單獨聲明出零件,然後在這裏進行組裝。

發佈了14 篇原創文章 · 獲贊 3 · 訪問量 2087
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章