SpringBoot 项目搭建及简单整合

项目整合了redis非关系型数据库,swagger2生成API,lombok插件,druid数据源,mybatis,mybatis-plus, 多数据源的配置,log4j2和logback日志,定时器,springboot的多环境配置,rabbitmq的整合及其延时消息的设置   

相关源码已上传至下载资源,链接为:

https://download.csdn.net/download/qq_39615245/10617010                                              

目录

1. 快速搭建一个 Spring Boot 项目 

1.1 搭建项目

1.2 项目结构

1.3 Spring Boot

1.4  Maven导包

1.5 测试下

2. SpringBoot整合MyBatis并添加分页插件

2.1 依赖Jar包

2.1.1 lombok插件

2.2 配置数据源信息

2.3  代码写入

2.3.1 创建UserMapper接口

2.3.2 创建service层

2.3.3 controller层中调用方法

2.3.5 xml配置文件开发版本

3. 整合日志(log4j2,logback)

3.1 整合log4j2

3.1.1 依赖jar包

3.1.2 配置文件

3.1.3 调用log输出日志

3.2 整合logback

3.2.1 依赖jar包

3.2.2  配置文件

3.2.3 添加yml或properties配置信息

3.2.4 调用logback输出日志

4. 整合Druid

4.1 什么是Druid?

4.2  依赖jar包

4.3 增加配置文件内容(yml为例)

4.4 添加log4j2.xml

4.5 测试

5. 整合Mybatis-Plus

5.1 什么是Mybatis-Plus

5.2 依赖jar包

5.3 application.yml文件配置

5.4 mybatis的配置,注入分页插件的Bean

5.5 逻辑实现

5.5.1 实体类的创建

5.5.2 创建表数据层控制接口StudentMapper

5.5.3 创建StudentMapper的配置文件

5.5.4 分页方法的使用

6. 整合Redis

6.1 依赖jar包

6.2 配置Redis信息

6.3 编写RedisConfig类

6.4 编写RedisDao类

6.5 调用redis方法

7. 整合swagger2

7.1 swagger是什么?

7.2 依赖jar包

7.3 编写swagger2配置类

7.4 启动类配置

7.5 Swagger2的注解意义

8 springBoot热部署

8.1 依赖jar包

8.2 setting设置

8.3 Registry…设置

9 springboot多数据源配置

9.1 yml文件配置

9.2 数据源配置文件

9.3 dao层和xml文件

10. 整合定时器

10.1 依赖jar包

10.2 创建RunTask类,继承QuartzJobBean,如下:

10.3 创建配置类QuartzConfig

10.4 Cron表达式详解

11. springboot的多环境配置

12  整合消息对列(RabbitMq)

12.1 安装RabbitMq(Windows版本)

12.2 RabbitMq工作模式

1.简单队列

2 工作对列

3 订阅模式

4 路由模式

5 主题模式(通配符模式)

12.3 整合延时消息

12.3.1 安装延时插件

12.3.2 整合代码



1. 快速搭建一个 Spring Boot 项目 

IDEA版本

2018.1.6

Springboot版本

2.0.3.RELEASE

jdk

1.8

Maven

Apache Maven 3.0.5

1.1 搭建项目

创建Spring Boot操作步骤如下:
1.在File菜单里面选择 New > Project,然后选择Spring Initializr。

2.写入项目的name,group等信息

3.勾选web中的web

4.finish

1.2 项目结构

根据上面的操作已经初始化了一个Spring Boot的框架了,项目结构如下:

如你所见,项目里面基本没有代码,除了几个空目录外,还包含如下几样东西。

1. pom.xml:Maven构建说明文件。

2. SpeedApplication.java:一个带有main()方法的类,用于启动应用程序(关键)。

3. SpeedApplicationTests.java:一个空的Junit测试类,它加载了一个使用Spring Boot字典配置功能的Spring应用程序上下文。

4. application.properties:一个空的properties文件,你可以根据需要添加配置属性。个人比较喜欢yml配置文件,后面的application配置文件会使用yml文件

1.3 Spring Boot

(1) 轻量化

(2) 提供 Spring 框架各种默认配置来简化项目配置

(3) 内嵌 Web 容器

(4) 没有冗余代码生成和XML配置要求

1.4  Maven导包

spring-boot-starter:核心模块,包括了自动配置支持、日志和YAML

spring-boot-starter-test:测试模块,包括JUnit、Hamcrest、Mockito

spring-boot-starter-web:Web模块

如果项目中main方法不能识别,点开maven工具栏clean install后即可

1.5 测试下

一个 Spring Boot 案例应该包括四个部分(主加载类、逻辑实现类、单元测试类、以及资源配置文件)。

1. 资源配置文件:这个文件主要记录了框架下各种设置;前面,我们提到过 Spring Boot 提供 Spring 的默认设置,所以一开始并不需要对这个文件做任何修改,让框架内嵌的Web容器加载该文件即可。

* 注意:命名为application.properties ,并且默认端口为8080

2. 主加载类:Spring Boot 框架下,最重要的一个类,也是启动整个框架的入口。一般有两种代码模板,好像也没有什么区别。这里先写一种:

@SpringBootApplication

public class SpeedApplication {

   public static void main(String[] args) {

      SpringApplication.run(SpeedApplication.class, args);

   }

}

3. 逻辑实现类:就是我们提供的服务接口,一般就是我们的Controller层。这里实现一个简单的”hello world!”的Controller,便于测试。 启动项目后,访问 http://localhost:8080/hello 来访问这个控制器: 

@RestController
public class HellCoroller {
    @RequestMapping("/hello")
    public String sayHello(){
        return "hello world!";
    }
}

4. 单元测试类:顾名思义,就是一个用来测试我们的逻辑实现类的类。下面的项目是新创建的promote项目

2. SpringBoot整合MyBatis并添加分页插件

2.1 依赖Jar包

<!--开发web项目相关依赖-->

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!-- MySQL Driver驱动包 -->

<dependency>

   <groupId>mysql</groupId>

   <artifactId>mysql-connector-java</artifactId>

   <version>5.1.38</version>

   <scope>runtime</scope>

</dependency>

<!-- mybatis配置 -->

<dependency>

   <groupId>org.mybatis.spring.boot</groupId>

   <artifactId>mybatis-spring-boot-starter</artifactId>

   <version>1.3.0</version>

</dependency>
<!-- springboot分页插件 -->

<dependency>

   <groupId>com.github.pagehelper</groupId>

   <artifactId>pagehelper-spring-boot-starter</artifactId>

   <version>1.2.3</version>

</dependency>
<!--lombok-->

<dependency>

   <groupId>org.projectlombok</groupId>

   <artifactId>lombok</artifactId>

   <scope>provided</scope>

   <version>1.16.20</version>

</dependency>

2.1.1 lombok插件

Lombok介绍: 在项目中使用Lombok可以减少很多重复代码的书写。比如说getter/setter/toString等方法的编写。

参考网址:https://blog.csdn.net/motui/article/details/79012846

在IDEA上安装lombok插件,流程如下:

Fileà-->setting-->Build,Execution,Deployment-->Annotationà选中Enable annotation processingàApply;

选择pluginsà搜素框搜索lombokà安装重启IDEA

2.2 配置数据源信息

spring:
 #数据源配置
  datasource:
    url: jdbc:mysql:///link_dev
    password: root
    username: root
    driver-class-name: com.mysql.jdbc.Driver

创建entity目录,创建User实体类

@Data
public class User {
    private Integer id;
    private String username;
    private Date ctm;
    private Integer age;
}
sql语句

CREATE TABLE `user` (
 `id` int(255) NOT NULL AUTO_INCREMENT,
  `userName` varchar(40) DEFAULT NULL,
  `ctm` date DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '钱', '2018-07-15', '25');
INSERT INTO `user` VALUES ('2', '王', '2018-07-11', '14');
INSERT INTO `user` VALUES ('3', '李', '2018-07-09', '23');
INSERT INTO `user` VALUES ('4', '吕', '2018-07-19', '1');
INSERT INTO `user` VALUES ('5', '张', '2018-07-15', '2');
INSERT INTO `user` VALUES ('6', '谢', '2018-07-16', '3');
INSERT INTO `user` VALUES ('7', '周', '2018-07-16', '4');
INSERT INTO `user` VALUES ('8', '云', '2018-07-16', '5');
INSERT INTO `user` VALUES ('9', '马', '2018-07-16', '6');

2.3  代码写入

2.3.1 创建UserMapper接口

创建mapper目录,增加UserMapper接口

package com.jhw.promote.mapper;

import com.jhw.promote.entity.User;

import org.apache.ibatis.annotations.*;

import org.springframework.stereotype.Repository;

import java.util.List;

/**

 * Author: 

 * CreateTime:2018/7/16  22:13

 * Desc:user表的操作数据的接口

 */

@Mapper//这里可以使用@Mapper注解,但是每个mapper都加注解比较麻烦,所以统一配置@MapperScan在扫描路径在application类中

@Repository

public interface UserMapper {

    @Select("SELECT * FROM user")

    List<User> selectUsers ();

    }

2.3.2 创建service层

 1.  创建service目录,创建UserService接口

package com.jhw.promote.service;
import com.jhw.promote.entity.User;
import java.util.List;
/**
 * Author: 
 * CreateTime:2018/7/16  22:19
 * Desc:
 */
public interface UserService {
List<User> getAllUser(int pageNum,int pageSize) ;
}

2.  在service中创建impl目录,创建UserServiceImpl实现类

package com.jhw.promote.service.impl;
import com.github.pagehelper.PageHelper;
import com.jhw.promote.dao.RedisDao;
import com.jhw.promote.entity.User;
import com.jhw.promote.mapper.UserMapper;
import com.jhw.promote.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * Author:
 * CreateTime:2018/7/16  22:21
 * Desc:
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    /*
     * 这个方法中用到了我们开头配置依赖的分页插件pagehelper
     * 很简单,只需要在service层传入参数,然后将参数传递给一个插件的一个静态方法即可;
     * pageNum 开始页数
     * pageSize 每页显示的数据条数
     * */
    @Override
    public List<User> getAllUser(int pageNum, int pageSize) {
        //将参数传给这个方法就可以实现物理分页了,非常简单。
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.selectUsers();
        return users;
    }
}

 

2.3.3 controller层中调用方法

依赖注入

@Autowired
private UserService userService;

调用方法

userService. getAllUser(pageNum,pageSize);

2.3.4 启动类中加载mapper

在mapper文件中可以不加@Mapper,在启动类上加注解引号中为mapper所在包的映射,即可:

@MapperScan("com.jhw.promote.mapper")

2.3.5 xml配置文件开发版本

1. 除了上述的注解sql查询整合方式外,springboot整合mybatis还可以使用xml配置文件的方式进行整合,在以上基础上,将UserMapper.java改为如下:                           注意 : @Mapper注解一定要增加

package com.jhw.promote.mapper;
import com.jhw.promote.entity.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
 * Author: 
 * CreateTime:2018/7/16  22:13
 * Desc:user表的操作数据的接口
 */
//@Mapper 这里可以使用@Mapper注解,但是每个mapper都加注解比较麻烦,所以统一配置@MapperScan在扫描路径在application类中
@Mapper
public interface UserMapper {
    List<User> selectUsers();
}

2. 在yml文件中添加配置

在resources目录下,创建mapper目录,并将之路径添加application.yml中如下:

mybatis:
  mapper-locations: classpath:mapper/*.xml   #xml文件所在目录
  type-aliases-package: com.jhw.promote.entity  #一般对应我们的实体类所在的包

3. 在mapper目录下配置xml文件

这种方式和普通的ssm框架格式差不多,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.jhw.promote.mapper.UserMapper" >
    <select id="selectUsers" resultType="com.jhw.promote.entity.User">
      SELECT
         id,
         userName,
         age,
         ctm
      FROM
         user
      order by
          ctm desc
  </select>
</mapper>

3. 整合日志(log4j2,logback)

特别提醒Spring Boot 只有1.3.x1.3.x以下版本才支持log4j的日志配置,1.3.x以上版本只支持log4j2logback的日志配置

3.1 整合log4j2

3.1.1 依赖jar包

<!-- Spring Boot web依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
      <exclusion>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
   </exclusions>
</dependency>
<!-- Spring Boot log4j2依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 加上这个才能辨认到log4j2.yml文件 -->
<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

3.1.2 配置文件

Resources目录下增加log4j2.yml文件,内容如下:

Configuration:  
  status: warn  
  Properties: # 定义全局变量  
    Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下:  
      #测试:-Dlog.level.console=warn -Dlog.level.jhw=trace  
      #生产:-Dlog.level.console=warn -Dlog.level.jhw=info        
      - name: log.level.console  
        value: trace  
      - name: log.level.jhw  
        value: trace  
      #  日志存储路径             
      - name: log.path  
        value: /opt/logs  
      # 日志名称       
      - name: project.name  
        value: promote
  Appenders:  
    Console:  #输出到控制台  
      name: CONSOLE  
      target: SYSTEM_OUT  
      ThresholdFilter:  
        level: ${sys:log.level.console} # “sys:”表示:如果VM参数中没指定这个变量值,则使用本文件中定义的缺省全局变量值  
        onMatch: ACCEPT  
        onMismatch: DENY  
      PatternLayout:  
        pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"  
    RollingFile: # 输出到文件,超过128MB归档  
      - name: ROLLING_FILE  
        ignoreExceptions: false  
        fileName: ${log.path}/${project.name}.log  
        filePattern: "${log.path}/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"  
        PatternLayout:  
          pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"  
        Policies:  
          SizeBasedTriggeringPolicy:  
            size: "128 MB"  
        DefaultRolloverStrategy:  
          max: 1000  
  Loggers:  
    Root:  
      level: info  
      AppenderRef:  
        - ref: CONSOLE  
        - ref: ROLLING_FILE  
    Logger: # 为com.jhw.promote包配置特殊的Log级别,方便调试  
      - name: com.jhw.promote  
        additivity: false  
        level: ${sys:log.level.jhw}  
        AppenderRef:  
          - ref: CONSOLE  
          - ref: ROLLING_FILE   

3.1.3 调用log输出日志

private static final Logger logger=LoggerFactory.getLogger(UserController.class);

在需要的地方调用logger.info(),debug(),error()等,

3.2 整合logback

3.2.1 依赖jar包

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <!-- 去除默认配置 日志为log4j2时需要配置 -->
   <!--<exclusions>-->
      <!--<exclusion>-->
         <!--<groupId>org.springframework.boot</groupId>-->
         <!--<artifactId>spring-boot-starter-logging</artifactId>-->
      <!--</exclusion>-->
   <!--</exclusions>-->
</dependency>
<!--logback依赖包-->
<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
</dependency>

3.2.2  配置文件

Resources目录下增加logback-spring.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">
    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="D:/opt/logs" />
    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--输出到文件-->
    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--<logger name="org.springframework.web" level="info"/>-->
    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->
    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.nmys.view" level="debug"/>
    </springProfile>
    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>
    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
    <!--<root level="info">-->
    <!--<appender-ref ref="CONSOLE" />-->
    <!--<appender-ref ref="DEBUG_FILE" />-->
    <!--<appender-ref ref="INFO_FILE" />-->
    <!--<appender-ref ref="ERROR_FILE" />-->
    <!--<appender-ref ref="WARN_FILE" />-->
    <!--</root>-->

 <!--</springProfile>-->
</configuration>

里面的输出路径根据自己的情况进行更改

3.2.3 添加yml或properties配置信息

#properties
logging.config=classpath:logback-spring.xml
-------------------------------------------------------
#yml
logging:
  config: classpath:logback-spring.xml

3.2.4 调用logback输出日志

private static final Logger logger=LoggerFactory.getLogger(UserController.class);

4. 整合Druid

4.1 什么是Druid?

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池(据说是目前最好的连接池)

4.2  依赖jar包

在pom.xml中增加依赖

<!-- druid -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.10</version>
</dependency>

4.3 增加配置文件内容(yml为例)

在application.yml中增加以下内容:

spring:
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      # 初始化时建立的连接数
      initial-size: 5
      # 最大连接数
      max-active: 20
      # 最小连接数
      min-idle: 5
      # 获取连接最大等待时间,单位:毫秒
      max-wait: 2000
      # 是否缓存preparedStatement
      pool-prepared-statements: false
      # 最大preparedStatement缓存数,当pool-prepared-statements=true时需要大于0
      max-pool-prepared-statement-per-connection-size: -1
      # 检测连接是否失效的sql
      validation-query: SELECT 'x'
      # 检测连接是否失效的超时时间,单位:秒
      validation-query-timeout: 2
      filters: stat,wall
      # Spring aop监控的包路径
      aop-patterns: com.jhw.promote.service.*
      # 数据库连接池监控统计插件
      web-stat-filter:
        enabled: true
        url-pattern: /*
        # 过滤掉如下请求
        exclusions: '*.gif,*.png,*.jpg,*.html,*.js,*.css,*.ico,/druid/*'
      # 数据库连接池监控页面插件
      stat-view-servlet:
        enabled: true
        #druid的访问路径,相当于配置web.xml
        url-pattern: '/druid/*'
        #允许清空统计数据
        reset-enable: true
        #登录账号密码
        login-username: root
        login-password: 12345678
        #ip白名单
        allow: 127.0.0.1
        #ip黑名单(优先级高于allow)
        deny:
      filter:
        # 监控统计
        stat:
          enabled: true
          db-type: mysql
          # 打印慢sql
          log-slow-sql: true
          # 超过200毫秒即为慢sql
          slow-sql-millis: 200
        # sql防火墙
        wall:
          enabled: true
          db-type: mysql
          #对认定的攻击sql进行日志输出 由于没有配置log4j2,会报错
          log-violation: false
          # 对认定的攻击sql抛出异常
          throw-exception: true
          config:
            # 是否允许下述操作
            alter-table-allow: false
            truncate-allow: false
            drop-table-allow: false
            update-where-none-check: true
            # metadata会暴露数据的表结构
            metadata-allow: false
        # 日志
        log4j2:
          enabled: true
          # log4j2仅记录druid的sql执行日志
          statement-log-enabled: false
          connection-log-enabled: false
          result-set-log-enabled: false
          statement-executable-sql-log-enable: true
# log4j2配置文件
logging:
  config: classpath:log4j2.xml

4.4 添加log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!--只接受程序中INFO级别的日志进行处理-->
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
        </Console>
        <!--处理DEBUG级别的日志,并把该日志放到logs/debug.log文件中-->
        <!--打印出DEBUG级别日志,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileDebug" fileName="./logs/debug.log"
                     filePattern="logs/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="DEBUG"/>
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
        <!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
        <RollingFile name="RollingFileInfo" fileName="./logs/info.log"
                     filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--只接受INFO级别的日志,其余的全部拒绝处理-->
                <ThresholdFilter level="INFO"/>
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
               <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
        <!--处理WARN级别的日志,并把该日志放到logs/warn.log文件中-->
        <RollingFile name="RollingFileWarn" fileName="./logs/warn.log"
                     filePattern="logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
           <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
        <!--处理error级别的日志,并把该日志放到logs/error.log文件中-->
        <RollingFile name="RollingFileError" fileName="./logs/error.log"
                     filePattern="logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR"/>
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
        <!--druid的日志记录追加器-->
        <RollingFile name="druidSqlRollingFile" fileName="./logs/druid-sql.log"
                     filePattern="logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %L %M - %msg%xEx%n"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </appenders>
   <loggers>
        <root level="INFO">
           <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
           <appender-ref ref="RollingFileDebug"/>
        </root>
        <!--记录druid-sql的记录-->
        <logger name="druid.sql.Statement" level="debug" additivity="false">
            <appender-ref ref="druidSqlRollingFile"/>
        </logger>
        <logger name="druid.sql.Statement" level="debug" additivity="false">
            <appender-ref ref="druidSqlRollingFile"/>
        </logger>
        <!--log4j2 自带过滤日志-->
        <Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
        <Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
        <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
        <logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
        <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
        <Logger name="org.crsh.plugin" level="warn" />
        <logger name="org.crsh.ssh" level="warn"/>
       <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
        <Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
        <logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
        <logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
        <logger name="org.thymeleaf" level="warn"/>
    </loggers>
</configuration>

4.5 测试

打开浏览器输入http://localhost:8080/druid 进入登录页面进行登录

5. 整合Mybatis-Plus

5.1 什么是Mybatis-Plus

Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生.有以下特性:

  • 无侵入:Mybatis-Plus Mybatis 的基础上进行扩展,只做增强不做改变,引入 Mybatis-Plus 不会对您现有的 Mybatis 构架产生任何影响,而且 MP 支持所有 Mybatis 原生的特性
  • 依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring
  • 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
  • 预防Sql注入:内置Sql注入剥离器,有效预防Sql注入攻击
  • 通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题
  • 支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
  • 支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper Model Service Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(P.S. Mybatis 官方的 Generator 更加强大!)
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere
  • 支持关键词自动转义:支持数据库关键词(orderkey……)自动转义,还可自定义关键词
  • 内置分页插件:基于Mybatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
  • 内置性能分析插件:可输出Sql语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询
  • 内置全局拦截插件:提供全表 delete update 操作智能分析阻断,预防误操作

5.2 依赖jar包

<dependency>

   <groupId>com.baomidou</groupId>

   <artifactId>mybatisplus-spring-boot-starter</artifactId>

   <version>1.0.4</version>

</dependency>

<dependency>

   <groupId>com.baomidou</groupId>

   <artifactId>mybatis-plus</artifactId>

   <version>2.0.7</version>

</dependency>

5.3 application.yml文件配置

#mybatis
mybatis-plus:
  mapper-locations: classpath:/mapper/**Mapper.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.jhw.promote.entity
  global-config:
    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 2
    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
    field-strategy: 2
    #驼峰下划线转换
    db-column-underline: true
    #刷新mapper 调试神器
    refresh-mapper: true
    #数据库大写下划线转换
    #capital-mode: true
    #序列接口实现类配置
    #key-generator: 
    #逻辑删除配置(下面3个配置)
    logic-delete-value: 0
    logic-not-delete-value: 1
    #自定义SQL注入器
    #sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector
    #自定义填充策略接口实现
    #meta-object-handler: com.baomidou.springboot.xxx
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false

5.4 mybatis的配置,注入分页插件的Bean

在config目录下创建MybatisPlusConfig

@Configuration
@MapperScan("com.jhw.promote.mapper*")//扫描dao或者是Mapper接口
public class MybatisPlusConfig {
    /**
     * mybatis-plus 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDialectType("mysql");
        return page;
    }
}

5.5 逻辑实现

5.5.1 实体类的创建

其中的注解中的字段和数据库中的表一一对应;

@Data //lombok的注解

@TableName("students")

public class Student {

    @TableId(value="id",type=IdType.AUTO)

    private Integer id;

    @TableField("stu_name")

    private String stuName;

    @TableField("stu_number")

    private String stuNumber;

    private Integer gender;

    private Integer age;

    private String password;

    @TableField("stu_mobile")

    private String stuMobile;

    @TableField("par_name")//家长姓名

    private String parName;

    @TableField("par_mobile")

    private String parMobile;

    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")

    @TableField("create_time")

    private Date createTime;

    @TableField("is_delete")

    private Integer isDelete;

}

sql语句

CREATE TABLE `students` (
  `id` int(40) NOT NULL AUTO_INCREMENT,
  `stu_name` varchar(45) DEFAULT NULL,
  `stu_number` varchar(45) DEFAULT NULL,
  `gender` int(40) DEFAULT NULL,
  `age` int(40) DEFAULT NULL,
  `password` varchar(40) DEFAULT NULL,
  `stu_mobile` varchar(10) DEFAULT NULL,
  `par_name` varchar(40) DEFAULT NULL,
  `par_mobile` varchar(40) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `is_delete` int(2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `students` VALUES ('1', '张', '1456', '1', '15', '123456', '1396563478', '张父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('2', '李', '1457', '1', '15', '123456', '1396563478', '李父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('3', '王', '1458', '1', '15', '123456', '1396563478', '王父', '139646578945', '2018-07-01 14:57:40', '1');
INSERT INTO `students` VALUES ('4', '云', '1459', '1', '15', '123456', '1396563478', '云父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('5', '田', '1455', '1', '15', '123456', '1396563478', '田父', '139646578945', '2018-07-01 14:57:40', '1');
INSERT INTO `students` VALUES ('6', '钱', '1454', '1', '15', '123456', '1396563478', '钱父', '139646578945', '2018-07-01 14:57:40', '1');

 

5.5.2 创建表数据层控制接口StudentMapper

Mapper目录下创建StudentMapper.java,内容如下:

@Repository
public interface StudentMapper {

    List<Student> findAllStudent();

    List<Student> findSomeColumn();

    void deleteById(Integer id);

    void updateByPrimarKeySelective(Student student);

    void saveStudent(Student student);

    List<Student> findAllStudentPage(Pagination page);

    @Select("select * from tb_student where gender = #{gender}")

    @Results({

            @Result(column="stu_name",property="stuName"),

            @Result(column="stu_mobile",property="stuMobile"),

            @Result(column="stu_number",property="stuNumber"),

            @Result(column="par_name",property="parName"),

            @Result(column="par_mobile",property="parMobile"),

            @Result(column="create_time",property="createTime")

    })

    List<Student> findStuByGender(Integer gender);

}

类中以两种方式进行了表的操作,注解和xml共同使用

 

5.5.3 创建StudentMapper的配置文件

在resource目录下创建StudentMaper.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.jhw.promote.mapper.StudentMapper">

    <resultMap id="BaseResultMap" type="com.jhw.promote.entity.Student">

        <id column="id" property="id"></id>

        <result column="stu_name" property="stuName"></result>

        <result column="stu_mobile" property="stuMobile"></result>

        <result column="stu_number" property="stuNumber"></result>

        <result column="create_time" property="createTime"></result>

        <result column="par_mobile" property="parMobile"></result>

        <result column="par_name" property="parName"></result>

        <result column="is_delete" property="isDelete"></result>

    </resultMap>

    <sql id="base_column_list">

        stu_name,stu_mobile,stu_number,create_time,par_mobile,par_name

    </sql>

    <!-- 拼接 -->

    <insert id="saveStudent" parameterType="com.jhw.promote.entity.Student">

        insert into students

        <trim prefix="(" suffix=")" suffixOverrides="," >

            <if test="stuName != null">

                stu_name,

            </if>

            <if test="stuMobile">

                stu_mobile,

            </if>

            <if test="stuNumber">

                stu_number,

            </if>

            <if test="roleId">

                role_id,

            </if>

            <if test="parMobile">

                par_mobile,

            </if>

            <if test="parName">

                par_name,

            </if>

        </trim>

        <trim prefix="values (" suffix=")" suffixOverrides=",">

            <if test="stuName != null">#{stuName},</if>

            <if test="stuMobile != null">#{stuMobile},</if>

            <if test="stuNumber!= null">#{stuNumber},</if>

            <if test="roleId !=null">#{roleId},</if>

            <if test="parMobile != null">#{parMobile},</if>

            <if test="parName !=null">#{parName},</if>

        </trim>

    </insert>

    <delete id="deleteById" parameterType="java.lang.Integer">

        delete from students

        where id = #{id}

    </delete>

    <update id="updateByPrimarKeySelective" parameterType="com.jhw.promote.entity.Student">

        update students

        <set>

            <if test="stuName ! = null">

                stu_name = #{stuName}

            </if>

            <if test="password ! =null">

                password = #{password}

            </if>

            <if test="stuMobile ! = null">

                stu_mobile = #{stuMobile}

            </if>

        </set>

    </update>

    <select id="findSomeColumn" resultMap="BaseResultMap">

        select

        <include refid="base_column_list" />

        from students

    </select>

    <select id="findAllStudent" resultMap="BaseResultMap">

        select * from students

    </select>

    <select id="findAllStudentPage" resultMap="BaseResultMap" resultType="com.jhw.promote.entity.Student">

        select * from students

    </select>

</mapper>

5.5.4 分页方法的使用

在controller中,使用如下内容:

Page page = new Page(pageNum,pageSize);

Page allStudentPage = studentService.findAllStudentPage(page);

return allStudentPage.getRecords();//类型为List

Service层就和平常的逻辑代码一样了

6. 整合Redis

 

6.1 依赖jar包

在pom.xml文件中添加以下jar包

<!--redis start-->

   <!--nosql数据库-->

   <dependency>

      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-starter-data-redis</artifactId>

   </dependency>

   <!--spring2.0集成redis所需common-pool2-->

   <dependency>

      <groupId>org.apache.commons</groupId>

      <artifactId>commons-pool2</artifactId>

      <version>2.4.2</version>

   </dependency>

   <!--fastjson-->

   <dependency>

      <groupId>com.alibaba</groupId>

      <artifactId>fastjson</artifactId>

      <version>1.2.8</version>

   </dependency>

<!--redis end-->

6.2 配置Redis信息

spring:

#Redis配置

  redis:

    #数据库索引

    database: 0

    host: 127.0.0.1

    port: 6379

    password:

    #连接超时时间

    timeout: 5000

    jedis:

      pool:

        #最大连接数

        max-active: 8

        #最大阻塞等待时间(负数表示没限制)

        max-wait: -1

        #最大空闲

        max-idle: 8

        #最小空闲

        min-idle: 0

 

6.3 编写RedisConfig类

在目录中建立config包,创建RedisConfig类:

package com.jhw.promote.config;

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.serializer.SerializerFeature;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.boot.autoconfigure.data.redis.RedisProperties;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.core.RedisOperations;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.data.redis.serializer.RedisSerializer;

import org.springframework.data.redis.serializer.SerializationException;

import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.nio.charset.Charset;

/**

 * Author:

 * CreateTime:2018/7/16  16:28

 * Desc:

 */

@Configuration

@ConditionalOnClass(RedisOperations.class)

@EnableConfigurationProperties(RedisProperties.class)

public class RedisConfig {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    @Bean

    @ConditionalOnMissingBean(name = "redisTemplate")

    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        //使用fastjson序列化

        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);

        // value值的序列化采用fastJsonRedisSerializer

        template.setValueSerializer(fastJsonRedisSerializer);

        template.setHashValueSerializer(fastJsonRedisSerializer);

        // key的序列化采用StringRedisSerializer

        template.setKeySerializer(new StringRedisSerializer());

        template.setHashKeySerializer(new StringRedisSerializer());



        template.setConnectionFactory(redisConnectionFactory);

        return template;

    }

    @Bean

    @ConditionalOnMissingBean(StringRedisTemplate.class)

    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        StringRedisTemplate template = new StringRedisTemplate();

        template.setConnectionFactory(redisConnectionFactory);

        return template;

    }

    public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

        private Class<T> clazz;

        public FastJsonRedisSerializer(Class<T> clazz) {

            super();

            this.clazz = clazz;

        }

        @Override

        public byte[] serialize(T t) throws SerializationException {

            if (null == t) {

                return new byte[0];

            }

            return JSON.toJSONString(t, SerializerFeature.WriteEnumUsingToString).getBytes(DEFAULT_CHARSET);

        }

        @Override

        public T deserialize(byte[] bytes) throws SerializationException {

            if (null == bytes || bytes.length <= 0) {

                return null;

            }

            String str = new String(bytes, DEFAULT_CHARSET);

            return (T) JSON.parseObject(str, clazz);

        }

    }

}

类中的redisTemplate为加载redis是的设置,

stringRedisTemplate为返回的redisTemplate,

FastJsonRedisSerializer是重写了FastJsonRedisSerializer的方法

6.4 编写RedisDao类

创建dao目录,新建RedisDao类:

package com.jhw.promote.dao;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.ValueOperations;

import org.springframework.data.redis.serializer.StringRedisSerializer;

import org.springframework.stereotype.Component;

import java.io.Serializable;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.TimeUnit;

/**

 * Author: 

 * CreateTime:2018/7/16  16:32

 * Desc:

 */

@SuppressWarnings("unchecked")

@Component

public class RedisDao {

    @SuppressWarnings("rawtypes")

    @Autowired

    private RedisTemplate redisTemplate;

    /**

     * 批量删除对应的value

     * @param keys

     */

    public void remove(final String... keys) {

        for (String key : keys) {

            remove(key);

        }

    }

    /**

     * 批量删除key

     * @param pattern

     */

    public void removePattern(final String pattern) {

        Set<Serializable> keys = redisTemplate.keys(pattern);

        if (keys.size() > 0)

            redisTemplate.delete(keys);

    }

    /**

     * 删除对应的value

     * @param key

     */

    public void remove(final String key) {

        if (exists(key)) {

            redisTemplate.delete(key);

        }

    }

    /**

     * 判断缓存中是否有对应的value

     * @param key

     * @return

     */

    public boolean exists(final String key) {

        return redisTemplate.hasKey(key);

    }

    /**

     * 读取缓存

     * @param key

     * @return

     */

    public String get(final String key) {

        Object result = null;

        redisTemplate.setValueSerializer(new StringRedisSerializer());

        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();

        result = operations.get(key);

        if(result==null){

            return null;

        }

        return result.toString();

    }

    /**

     * 写入缓存

     * @param key

     * @param value

     * @return

     */

    public boolean set(final String key, Object value) {

        boolean result = false;

        try {

            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();

            operations.set(key, value);

            result = true;

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }

    /**

     * 写入缓存

     * @param key

     * @param value

     * @return

     */

    public boolean set(final String key, Object value, Long expireTime) {

        boolean result = false;

        try {

            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();

            operations.set(key, value);

            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);

            result = true;

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }

    /**

     * 批量写入map

     * @param key

     * @param value

     * @return

     */

    public  boolean hmset(String key, Map<String, String> value) {

        boolean result = false;

        try {

            redisTemplate.opsForHash().putAll(key, value);

            result = true;

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }

    /**

     * 获取map

     * @param key

     * @return

     */

    public  Map<String,String> hmget(String key) {

        Map<String,String> result =null;

        try {

            result=  redisTemplate.opsForHash().entries(key);

        } catch (Exception e) {

            e.printStackTrace();

        }

        return result;

    }

}

方法中为基本的redis操作方法

6.5 调用redis方法

在需要用到redis的类中注入RedisDao,调用相应方法即可

@Autowired
private RedisDao redisDao;

 

7. 整合swagger2

7.1 swagger是什么?

Swagger是接口管理工具(Api文档工具),通过注解的方式将所有接口集中到一起,并通过页面显示出来,相当于接口文档,方便开发者开发和前后端对接

7.2 依赖jar包

在pom.xml中加入以下依赖:

<!--swagger2 依赖jar包-->

<dependency>

   <groupId>io.springfox</groupId>

   <artifactId>springfox-swagger2</artifactId>

   <version>2.6.1</version>

</dependency>

<dependency>

   <groupId>io.springfox</groupId>

   <artifactId>springfox-swagger-ui</artifactId>

   <version>2.6.1</version>

</dependency>

7.3 编写swagger2配置类

在config目录中创建Swagger2类:

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;

import springfox.documentation.builders.PathSelectors;

import springfox.documentation.builders.RequestHandlerSelectors;

import springfox.documentation.service.ApiInfo;

import springfox.documentation.spi.DocumentationType;

import springfox.documentation.spring.web.plugins.Docket;

/**

 * Author: 

 * CreateTime:2018/7/16  19:34

 * Desc:

 */

@Configuration

public class Swagger2 {

    @Bean

    public Docket createRestApi() {

        return new Docket(DocumentationType.SWAGGER_2)

                .apiInfo(apiInfo())

                .select()

                .apis(RequestHandlerSelectors.basePackage("com.jhw.promote.controller"))

                .paths(PathSelectors.any())

                .build();

    }

    private ApiInfo apiInfo() {

        return new ApiInfoBuilder()

                .title("springboot利用swagger构建api文档")

                .description("简单优雅的restfun风格,https://blog.csdn.net/qq_39615245/article/details/81139180")

                .termsOfServiceUrl("https://blog.csdn.net/qq_39615245")

                .version("1.0")

                .build();

    }

}

注意:RequestHandlerSelectors.basePackage("com.jhw.promote.controller")的引号内为controller的路径

7.4 启动类配置

在启动类上添加注解:

@EnableSwagger2

7.5 Swagger2的注解意义

@Api:用在请求的类上,表示对类的说明

    tags="说明该类的作用,可以在UI界面上看到的注解"

value="该参数没什么意义,在UI界面上也看到,所以不需要配置"

description=”描述”

@Api(value =" " ,tags = "UserController", description = "用户访问类")

 

@ApiOperation:用在请求的方法上,说明方法的用途、作用

    value="说明方法的用途、作用"

    notes="方法的备注说明"

 @ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息")

 

@ApiImplicitParams:用在请求的方法上,表示一组参数说明

    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面

        name:参数名

        value:参数的汉字说明、解释

        required:参数是否必须传

        paramType:参数放在哪个地方

            · header --> 请求参数的获取:@RequestHeader

            · query --> 请求参数的获取:@RequestParam

            · path(用于restful接口)--> 请求参数的获取:@PathVariable

            · body(不常用)

            · form(不常用)   

        dataType:参数类型,默认String,其它值dataType="Integer"      

        defaultValue:参数的默认值

 @ApiImplicitParams({

        @ApiImplicitParam(name="mobile",value="手机号",required=true,paramType="form"),

        @ApiImplicitParam(name="password",value="密码",required=true,paramType="form"),

@ApiImplicitParam(name="age",value="年龄",required=true,paramType="form",dataType="Integer")

})

@ApiResponses:用在请求的方法上,表示一组响应

    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息

        code:数字,例如400

        message:信息,例如"请求参数没填好"

        response:抛出异常的类

 @ApiOperation(value = "select1请求",notes = "多个参数,多种的查询参数类型")

@ApiResponses({

        @ApiResponse(code=400,message="请求参数没填好"),

        @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对")

})

 

@ApiModel:用于响应类上,表示一个返回响应数据的信息

            (这种一般用在post创建的时候,使用@RequestBody这样的场景,

            请求参数无法使用@ApiImplicitParam注解进行描述的时候)

    @ApiModelProperty:用在属性上,描述响应类的属性

package com.jhw.promote.entity;

/**

 * Author: 

 * CreateTime:2018/7/16  21:28

 * Desc:

 */

import io.swagger.annotations.ApiModel;

import io.swagger.annotations.ApiModelProperty;

import lombok.Data;

import java.io.Serializable;

@ApiModel(description= "返回响应数据")

@Data

public class RestMessage implements Serializable{

    @ApiModelProperty(value = "是否成功")

    private boolean success=true;

    @ApiModelProperty(value = "返回对象")

    private Object data;

    @ApiModelProperty(value = "错误编号")

    private Integer errCode;

    @ApiModelProperty(value = "错误信息")

    private String message;

}

浏览器中输入http://localhost:8080/swagger-ui.html# 在页面上的提现如下:

8 springBoot热部署

8.1 依赖jar包

<!--热部署-->

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-devtools</artifactId>

   <optional>true</optional>

</dependency>
<build>

   <plugins>

      <!--maven构建-->

      <plugin>

         <groupId>org.springframework.boot</groupId>

         <artifactId>spring-boot-maven-plugin</artifactId>

         <configuration>

            <fork>true</fork>

         </configuration>

      </plugin>

   </plugins>

</build>

8.2 setting设置

Setting-->compile-->勾选 Build project automatically-->ok

 

8.3 Registry…设置

Ctrl+Shift+Alt+/ => Register… => 找到并勾选compiler.automake.allow.when.app.running => IDEA重启

 

9 springboot多数据源配置

在项目开发中,会遇到要用不同数据库的数据这种情况,这是就需要多数据源的使用了,配置多数据源有多种方法:jpa,spring,aop等方法,以下这种方法属于spring,提供数据支持

9.1 yml文件配置

在此配置了两个数据源,如果想要更多,据需添加即可

spring:

  datasource:

    type: com.alibaba.druid.pool.DruidDataSource

#    数据源1

    primary:

      driver-class-name: com.mysql.jdbc.Driver

      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8

      username: root

      password: root

##    数据源2

    secondary:

      driver-class-name: com.mysql.jdbc.Driver

      url: jdbc:mysql://localhost:3306/link?useUnicode=true&characterEncoding=utf8

      username: root

      password: root

    druid:

      # 初始化时建立的连接数

      initial-size: 5

9.2 数据源配置文件

在config目录下创建DataSource1Config.java文件

@MapperScan(basePackages="com.jhw.promote.mapper.secondaryDb",sqlSessionTemplateRef  = "secondSqlSessionTemplate")很关键, 这块的注解就是指明了扫描dao层,并且给dao层注入指定的SqlSessionTemplate。所有@Bean都需要按照命名指定正确
 

package com.jhw.promote.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**

 * Author:

 * CreateTime:2018/7/20  9:08

 * Desc:数据源1的配置—加载sessionfactory

 */

@Configuration

@MapperScan(basePackages = "com.jhw.promote.mapper.primaryDb", sqlSessionTemplateRef  = "primarySqlSessionTemplate")

public class DataSource1Config {

    @Bean(name = "primaryDataSource")

    @ConfigurationProperties(prefix = "spring.datasource.primary")

    @Primary

    public DataSource primaryDataSource() {

        return DruidDataSourceBuilder.create().build();

    }

    @Bean(name = "primarySqlSessionFactory")

    @Primary

    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {

        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

        bean.setDataSource(dataSource);
//这里是在resources目录下建立的mapper/ primary,里面必须有xml文件,如果是注解式开发,这不需要这行代码

        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));

        return bean.getObject();

    }

    @Bean(name = "primaryTransactionManager")

    @Primary

    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {

        return new DataSourceTransactionManager(dataSource);

    }

    @Bean(name = "primarySqlSessionTemplate")

    @Primary

    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

        return new SqlSessionTemplate(sqlSessionFactory);

    }

}

创建DataSource2Config.java文件

package com.jhw.promote.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;



/**

 * Author: 

 * CreateTime:2018/7/20  9:08

 * Desc:数据源2的配置—加载sessionfactory

 */

@Configuration

@MapperScan(basePackages = "com.jhw.promote.mapper.secondaryDb", sqlSessionTemplateRef  = "secondSqlSessionTemplate")

public class DataSource2Config {

    @Bean(name = "secondDataSource")

    @ConfigurationProperties(prefix = "spring.datasource.secondary")

    public DataSource secondDataSource() {

        return DruidDataSourceBuilder.create().build();

    }

    @Bean(name = "secondSqlSessionFactory")

    public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {

        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

        bean.setDataSource(dataSource);
  //这里是在resources目录下建立的mapper/second,里面必须有xml文件,如果是注解式开发,这不需要这行代码

        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/second/*.xml"));

        return bean.getObject();

    }

    @Bean(name = "secondTransactionManager")

    public DataSourceTransactionManager secondTransactionManager(@Qualifier("secondDataSource") DataSource dataSource) {

        return new DataSourceTransactionManager(dataSource);

    }

    @Bean(name = "secondSqlSessionTemplate")

    public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

        return new SqlSessionTemplate(sqlSessionFactory);

    }

}

9.3 dao层和xml文件

dao层和xml需要按照库来分在不同的目录

primarydao层在com.jhw.promote.mapper.primary.包下,secondary库在com.jhw.promote.mapper. second包下,

primary的xmlresources/mapper/primary目录下,secondary的xmlresources/mapper/ second目录下

调用不同的数据源时,调用相应的mapper就可以了(将启动类和mybatis-plus配置类上的mapperScan注解注释掉)

 

10. 整合定时器

定时任务实现的几种方式:

1. Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。

2. ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

3. Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

4. Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。

10.1 依赖jar包

Springboot2.0.0 以后, 则在spring-boot-starter中已经包含了quart的依赖,则可以直接使用spring-boot-starter-quartz依赖:

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-quartz</artifactId>

</dependency>
如果是1.5.9则要使用以下添加依赖:
<dependency>

   <groupId>org.quartz-scheduler</groupId>

   <artifactId>quartz</artifactId>

   <version>2.3.0</version>

</dependency>

<dependency>

   <groupId>org.springframework</groupId>

   <artifactId>spring-context-support</artifactId>

</dependency>

10.2 创建RunTask类,继承QuartzJobBean,如下:

package com.jhw.promote.task;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
/**
 * Author: 
 * CreateTime:2018/7/24  10:05
 * Desc:
 */
public class RunTask extends QuartzJobBean {
    /**
     * 执行定时任务
     * @param jobExecutionContext
     * @throws JobExecutionException
     */
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("quartz task " + new Date());
    }
}

10.3 创建配置类QuartzConfig

主要有两种方式,一种是SimpleScheduleBuilder,另一种是CronScheduleBuilder

  1. SimpleScheduleBuilder:只能设置时间单位周期,即:每隔多长时间进行一次调度
    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail teatQuartzDetail(){
            return JobBuilder.newJob(TestQuartz.class).withIdentity("RunTask").storeDurably().build();
        }
        @Bean
        public Trigger testQuartzTrigger(){
            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(10)  //设置时间周期单位秒
                    .repeatForever();
            return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())
                    .withIdentity("RunTask ")
                    .withSchedule(scheduleBuilder)
                    .build();
        }
    }
  2. CronScheduleBuilder:通过cron表达式能够精确表达调度时间,用的较多
    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail QuartzDetail(){
            return JobBuilder.newJob(RunTask.class).withIdentity("RunTask").storeDurably().build();
        }
        @Bean
        public Trigger QuartzTrigger(){
            return TriggerBuilder.newTrigger().forJob(QuartzDetail())
                    .withIdentity("RunTask")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? "))
                    .build();
        }
    }

10.4 Cron表达式详解

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: 

Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Seconds Minutes Hours DayofMonth Month DayofWeek

每一个域可出现的字符如下: 
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数 
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数 
Hours:可出现", - * /"四个字符,有效范围为0-23的整数 
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数 
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc 
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推 
Year:可出现", - * /"四个字符,有效范围为1970-2099年

每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是: 
(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。

(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。 
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次 
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次. 
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。 
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。 
(7)W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。 
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

举几个例子: 
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务 
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作

一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。 

按顺序依次为 
秒(0~59) 
分钟(0~59) 
小时(0~23) 
天(月)(0~31,但是你需要考虑你月的天数) 
月(0~11) 
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) 
年份(1970-2099)

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 
0 0 12 ? * WED 表示每个星期三中午12点 
"0 0 12 * * ?" 每天中午12点触发 
"0 15 10 ? * *" 每天上午10:15触发 
"0 15 10 * * ?" 每天上午10:15触发 
"0 15 10 * * ? *" 每天上午10:15触发 
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发 
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 
"0 15 10 15 * ?" 每月15日上午10:15触发 
"0 15 10 L * ?" 每月最后一日的上午10:15触发 
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

有些子表达式能包含一些范围或列表

例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

“*”字符代表所有可能的值

因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量 
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟 
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值 
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写 
但是它在两个子表达式里的含义是不同的。 
在天(月)子表达式中,“L”表示一个月的最后一天 
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT

如果在“L”前有具体的内容,它就具有其他的含义了

例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五 
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题

字段 允许值 允许的特殊字符 
秒 0-59 , - * / 
分 0-59 , - * / 
小时 0-23 , - * / 
日期 1-31 , - * ? / L W C 
月份 1-12 或者 JAN-DEC , - * / 
星期 1-7 或者 SUN-SAT , - * ? / L C # 
年(可选) 留空, 1970-2099 , - * /

2)Cron表达式范例:

                 每隔5秒执行一次:*/5 * * * * ?

                 每隔1分钟执行一次:0 */1 * * * ?

                 每天23点执行一次:0 0 23 * * ?

                 每天凌晨1点执行一次:0 0 1 * * ?

                 每月1号凌晨1点执行一次:0 0 1 1 * ?

                 每月最后一天23点执行一次:0 0 23 L * ?

                 每周星期天凌晨1点实行一次:0 0 1 ? * L

                 在26分、29分、33分执行一次:0 26,29,33 * * * ?

                 每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

11. springboot的多环境配置

创建如下文件:

不同环境的配置设置一个配置文件,例如:dev环境下的配置配置在application-dev.properties中;prod环境下的配置配置在application-prod.properties中。

在application.properties中指定使用哪一个文件

spring:
  profiles:
    active: prod
application-dev.yml(dev环境下的配置)
spring:
  profiles: dev
application-prod.yml(prod环境下的配置)
spring:
  profiles: prod
测试:
@Autowired
private Environment env;
@RequestMapping("/testProfile")
public String testProfile(){
    System.out.println(env.getProperty("spring.profiles"));
    System.out.println(env.getProperty("spring.profiles.active"));
    return env.getProperty("spring.profiles");
 }
访问接口,返回值正确即可,启动是,控制台日志中有如下记录:

2018-07-25 08:31:44.549  INFO 7664 --- [  restartedMain] com.jhw.promote.PromoteApplication       : The following profiles are active: dev

12  整合消息对列(RabbitMq)

1.从社区活跃度

按照目前网络上的资料,RabbitMQ 、activeM 、ZeroMQ 三者中,综合来看,RabbitMQ 是首选。 

2.持久化消息比较

ZeroMq 不支持,ActiveMq 和RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。

3.综合技术实现

可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。

RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然ZeroMq 也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。

4.高并发

毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言。

5.比较关注的比较, RabbitMQ 和 Kafka

RabbitMq 比Kafka 成熟,在可用性上,稳定性上,可靠性上,  RabbitMq  胜于  Kafka  (理论上)。

另外,Kafka 的定位主要在日志等方面, 因为Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。

还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多。

12.1 安装RabbitMq(Windows版本)

(1):下载erlang,原因在于RabbitMQ服务端代码是使用并发式语言erlang编写的,下载地址:http://www.erlang.org/downloads,双击.exe文件进行安装就好,安装完成之后创建一个名为ERLANG_HOME的环境变量,其值指向erlang的安装目录,同时将%ERLANG_HOME%\bin加入到Path中,最后打开命令行,输入erl,如果出现erlang的版本信息就表示erlang语言环境安装成功;

(2):下载RabbitMQ,下载地址:http://www.rabbitmq.com/install-windows.html,同样双击.exe进行安装就好,环境变量配置如下:

(3) 激活rabbitmq_management

1.cmd命令窗中键入命令:"D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.3\sbin\rabbitmq-plugins.bat" enable rabbitmq_management 2.启动mq
 net start RabbitMQ
3.停止mq
 net stop RabbitMQ
4.测试
 测试地址: http://localhost:15672/ 
 默认的用户名:guest 
 默认的密码为:guest
 成功即可

12.2 RabbitMq工作模式

1.简单队列

依赖jar包:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件添加内容:
spring:
 #rabbitmq配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  #  listener:
  #    simple:
  #      prefetch: 1
在config目录创建配置文件RabbitMQConfig,内容如下:
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * Author: 
 * CreateTime:2018/8/9  9:41
 * Desc:RabbitMQ配置
 */
@Configuration
public class RabbitMQConfig {
    private static final String QUEUE_MODEL1_NAME = "test_model1_queue";
    @Bean
    public Queue queue1(){
    /**
     * amqp_queue_declare
     * durable
     *   是否持久化
     * exclusive
     *   是否为私有队列,如果设为true则为私有,只有自己的应用程序能够消费当前队列消息。
     * autoDelete
     *   当没有消费者的时候队列是否自动删除(但是如果自始至终都没有消费者订阅的话,那么即不存在消费者,
     *   队列不会自动删除。之后当存在消费者之后当消费者为0的时候自动删除)
     * arguments
     *   arguments 填充amqp_empty_table
     * name
     *   队列名称
     * @returns amqp_queue_declare_ok_t
     */
        return new Queue(QUEUE_MODEL1_NAME, false, false, false, null);

    }

}
创建消息生产者,为方便测试,将生产者写为接口直接调取
@Autowired
private AmqpTemplate amqpTemplate;
@RequestMapping(value="/rabbitMq1",method = RequestMethod.GET)
public void testSimpleQueue() {
    String message = "Hello RabbitMQ !";
    amqpTemplate.convertAndSend("test_model1_queue", message);
    System.out.println("[model1] send " + message + " ok");
}
创建消费者,在目录中创建listener目录,创建SimpleRecv类,监听消息
@RabbitListener(queues = "test_model1_queue")
@Component
public class SimpleRecv {
    @RabbitHandler
    public void process(String message) {
        System.out.println("[model1] rev : " + message);
    }
}
运行项目,访问接口,控制台输出如下:

[model1] send Hello RabbitMQ ! ok

[model1] rev : Hello RabbitMQ !

2 工作对列

工作队列分两种情况:

轮询分发:不管消费者处理速度性能快慢,每个消费者都是按顺序分别每个拿一个的原则,比如3个消费者, 消费者1拿1个,然后消费者2拿一个,然后消费者3拿一个,然后消费者1开始拿,即使中间有消费者已经处理完了,也必须等待其他消费者都拿完一个才能消费到。

公平分发:根据消费者处理性能,性能好的消费的数据量多,性能差的消费的数据量少,这种情况也是日常工作中使用最为正常的,轮询模式用的较少,区别在于prefetch默认是1,如果设置为0就是轮询模式。

公平分发模式:

RabbitMQConfig中增加如下代码:

private static final String QUEUE_MODEL2_NAME = "test_model2_queue";
@Bean
public Queue queue2(){
    return new Queue(QUEUE_MODEL2_NAME, false, false, false, null);
}
增加生产者(接口形式):
@RequestMapping(value="/rabbitMq2",method = RequestMethod.GET)
public void testWorkFairQueue() {
    for (int i = 0; i < 20; i++) {
        String message = "Hello RabbitMQ " + i;
        // 发送消息
        amqpTemplate.convertAndSend("test_model2_queue", message);
        System.out.println("[model2] Sent '" + message + "'");
    }
}
Listener目录中创建多个消费者WorkRecv1
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * Author: 
 * CreateTime:2018/8/9  15:29
 * Desc:
 */
@RabbitListener(queues = "test_model2_queue")
@Component
public class WorkRecv1 {
    @RabbitHandler
    public void process(String message){
        System.out.println("[1] rev : " + message);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
    }
}
WorkRecv2
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * Author: 
 * CreateTime:2018/8/9  15:29
 * Desc:
 */
@RabbitListener(queues = "test_model2_queue")
@Component
public class WorkRecv2 {
    @RabbitHandler
    public void process(String message){
        System.out.println("[2] rev : " + message);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
运行项目,访问接口,控制台输出:
[model2] Sent 'Hello RabbitMQ 0'
...
[model2] Sent 'Hello RabbitMQ 19'
[1] rev : Hello RabbitMQ 0
[2] rev : Hello RabbitMQ 1
[1] rev : Hello RabbitMQ 2
[1] rev : Hello RabbitMQ 3
[1] rev : Hello RabbitMQ 4
[2] rev : Hello RabbitMQ 5
[1] rev : Hello RabbitMQ 6
[1] rev : Hello RabbitMQ 7
[1] rev : Hello RabbitMQ 8
[2] rev : Hello RabbitMQ 9
[1] rev : Hello RabbitMQ 10
[1] rev : Hello RabbitMQ 11
[1] rev : Hello RabbitMQ 12
[2] rev : Hello RabbitMQ 13
[1] rev : Hello RabbitMQ 14
[1] rev : Hello RabbitMQ 15
[1] rev : Hello RabbitMQ 16
[2] rev : Hello RabbitMQ 17
[1] rev : Hello RabbitMQ 18
[1] rev : Hello RabbitMQ 19

可以看出,消费者1和2获得的消息不等同

轮询分发模式: 在配置文件中, prefetch值设置为0,运行项目,访问接口

[model2] Sent 'Hello RabbitMQ 0'
...
[model2] Sent 'Hello RabbitMQ 19'
[1] rev : Hello RabbitMQ 0
[2] rev : Hello RabbitMQ 1
[1] rev : Hello RabbitMQ 2
[2] rev : Hello RabbitMQ 3
[1] rev : Hello RabbitMQ 4
[1] rev : Hello RabbitMQ 6
[2] rev : Hello RabbitMQ 5
[1] rev : Hello RabbitMQ 8
[1] rev : Hello RabbitMQ 10
[2] rev : Hello RabbitMQ 7
[1] rev : Hello RabbitMQ 12
[1] rev : Hello RabbitMQ 14
[2] rev : Hello RabbitMQ 9
[1] rev : Hello RabbitMQ 16
[1] rev : Hello RabbitMQ 18
[2] rev : Hello RabbitMQ 11
[2] rev : Hello RabbitMQ 13
[2] rev : Hello RabbitMQ 15
[2] rev : Hello RabbitMQ 17
[2] rev : Hello RabbitMQ 19

3 订阅模式

订阅模式即一个生产者发送消息给多个消费者,且每个消费者都收到一次,也即是一个消息能够被多个消费者消费。类似于我们订阅同一微信公众号,微信公众号推送图文,我们每个人都能收到一份。

交换机有几种类型,分别是:direct, topic, headers, fanout这里我们只讨论一种,就是 fanout,生产者发送消息给交换机,交换机分发绑定在交换机上的队列,每个队列对应一个消费者。

在RabbitMQConfig中加入以下内容,定义交换机、队列、以及队列与交换机的绑定关系:

/**
 * 订阅模式
 */
private static final String EXCHANGE_FANNOUT_NAME = "test_exchange_fanout";
private static final String QUEUE_PS_SMS_NAME = "test_queue_fanout_sms";
private static final String QUEUE_PS_EMAIL_NAME = "test_queue_fanout_email";
//注入交换机
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange(){
    return new FanoutExchange(EXCHANGE_FANNOUT_NAME);
}
//定义两个队列
@Bean
public Queue fanoutSmsQueue(){
    return new Queue(QUEUE_PS_SMS_NAME, false, false, false, null);
}
@Bean
public Queue fanoutEmailQueue(){
   return new Queue(QUEUE_PS_EMAIL_NAME, false, false, false, null);
}
//将两个队列和交换机绑定 
@Bean
public Binding smsQueueExchangeBinding(FanoutExchange fanoutExchange, Queue fanoutSmsQueue){
    return BindingBuilder.bind(fanoutSmsQueue).to(fanoutExchange);
}
@Bean
public Binding emailQueueExchangeBinding(FanoutExchange fanoutExchange, Queue fanoutEmailQueue){
    return BindingBuilder.bind(fanoutEmailQueue).to(fanoutExchange);
}
创建生产者(接口形式):
/**
 * 订阅模式
 */
@RequestMapping(value="/rabbitMqFanout",method = RequestMethod.GET)
public void testFanoutQueue() {
    String message = "Hello, fanout message ";
    // 发送消息
    amqpTemplate.convertAndSend("test_exchange_fanout", "", message);
    System.out.println("[Fanout] Sent '" + message + "'");
}
创建多个消费者: 
EmailRecv
/**
 * Author: 
 * CreateTime:2018/8/11  12:21
 * Desc:email消费者
 */
@RabbitListener(queues = "test_queue_fanout_email")
@Component
public class EmailRecv {
    @RabbitHandler
    public void process(String message){
        System.out.println("[email] rev : " + message);
    }
}
SmsRecv
/**
 * Author: 
 * CreateTime:2018/8/11  12:20
 * Desc:sms消费者
 */
@RabbitListener(queues = "test_queue_fanout_sms")
@Component
public class SmsRecv {
    @RabbitHandler
    public void process(String message){
        System.out.println("[sms] rev : " + message);
    }
}
运行项目,访问接口,结果如下:
[Fanout] Sent 'Hello, fanout message '
[sms] rev : Hello, fanout message 
[email] rev : Hello, fanout message

4 路由模式

跟订阅模式类似,只不过在订阅模式的基础上加上了类型,订阅模式是分发到所有绑定到交换机的队列,路由模式只分发到绑定在交换机上面指定路由键的队列。

在RabbitMQConfig中加入以下内容:

/**
 * 路由模式
 */
private static final String EXCHANGE_DIRECT_NAME = "test_exchange_direct";
private static final String QUEUE_PS_INFO_NAME = "test_queue_direct_info";
private static final String QUEUE_PS_ERROR_NAME = "test_queue_direct_error";
//注入交换机
@Bean("directExchange")
public DirectExchange directExchange(){
    return new DirectExchange(EXCHANGE_DIRECT_NAME);
}
//定义两个队列
@Bean
public Queue directInfoQueue(){
    return new Queue(QUEUE_PS_INFO_NAME, false, false, false, null);
}
@Bean
public Queue directErrorQueue(){
    return new Queue(QUEUE_PS_ERROR_NAME, false, false, false, null);
}
//将两个队列和交换机绑定  BindingBuilder.bind(队列).to(交换机).with(routing_key)---路由键;
@Bean
public Binding infoQueueExchangeBinding(DirectExchange directExchange, Queue directInfoQueue){
    return BindingBuilder.bind(directInfoQueue).to(directExchange).with("info");
}
@Bean
public Binding errorQueueExchangeBinding(DirectExchange directExchange, Queue directErrorQueue){
    return BindingBuilder.bind(directErrorQueue).to(directExchange).with("error");
}
创建生产者:
/**
 * 订阅模式
 */
@RequestMapping(value="/rabbitMqDirect",method = RequestMethod.GET)
public void testDirectQueue() {
    String message1 = "Direct error ";
    String message2 = "Direct info ";
    String message3 = "Direct message ";
    // 发送消息
    amqpTemplate.convertAndSend("test_exchange_direct", "error", message1);
    amqpTemplate.convertAndSend("test_exchange_direct", "info", message2);
    amqpTemplate.convertAndSend("test_exchange_direct", "", message3);
    System.out.println("[Direct] Sent '" + message1 + "'|'"+ message2 +"'|'"+ message3+"'");
}
创建多个消费者:
DirectError
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * Author: 
 * CreateTime:2018/8/11  13:15
 * Desc:
 */
@RabbitListener(queues = "test_queue_direct_error")
@Component
public class DirectError {
    @RabbitHandler
    public void process(String message){
        System.out.println("[error] rev : " + message);
    }
}
DirectInfo
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
 * Author: 
 * CreateTime:2018/8/11  13:15
 * Desc:
 */
@RabbitListener(queues = "test_queue_direct_info")
@Component
public class DirectInfo {
    @RabbitHandler
    public void process(String message){
        System.out.println("[info] rev : " + message);
    }
}
运行项目,访问接口,结果如下:
[Direct] Sent 'Direct error '|'Direct info '|'Direct message '
[info] rev : Direct info 
[error] rev : Direct error

有结果看出:消息3由于没有写入路由键,致使没有被消费者接收到

5 主题模式(通配符模式)

跟路由模式类似,只不过路由模式是指定固定的路由键,而主题模式是可以模糊匹配路由键,类似于SQL中=和like的关系。

在RabbitMQConfig中加入以下内容:

/**
 * 通配符模式(主题模式)
 */
private static final String EXCHANGE_TOPIC_NAME = "test_exchange_topic";
private static final String QUEUE_TOPIC_INFO_NAME = "test_queue_topic_info";
private static final String QUEUE_TOPIC_ERROR_NAME = "test_queue_topic_error";
//注入交换机
@Bean("topictExchange")
public TopicExchange topicExchange(){
    return new TopicExchange(EXCHANGE_TOPIC_NAME);
}
//定义两个队列
@Bean
public Queue topicInfoQueue(){
    return new Queue(QUEUE_TOPIC_INFO_NAME, false, false, false, null);
}
@Bean
public Queue topicErrorQueue(){
    return new Queue(QUEUE_TOPIC_ERROR_NAME, false, false, false, null);
}
//将两个队列和交换机绑定  BindingBuilder.bind(队列).to(交换机).with(routing_key)---路由键;
//*表示精确到一个单词,key.*只能匹配如key.word这样的词,而key.#可以是0个或多个
@Bean
public Binding topic_InfoQueueExchangeBinding(TopicExchange topicExchange, Queue topicInfoQueue){
    return BindingBuilder.bind(topicInfoQueue).to(topicExchange).with("info.*.*");
}
@Bean
public Binding topic_ErrorQueueExchangeBinding(TopicExchange topicExchange, Queue topicErrorQueue){
    return BindingBuilder.bind(topicErrorQueue).to(topicExchange).with("#.error.#");

}
创建生产者:
/**
 * 通配符模式(主题模式)
 */
@RequestMapping(value="/rabbitMqTopic",method = RequestMethod.GET)
public void testTopicQueue() {
    // 发送消息
    amqpTemplate.convertAndSend("test_exchange_topic", "info.1", "info.1");
    amqpTemplate.convertAndSend("test_exchange_topic", "info.1.2", "info.1.2");
    amqpTemplate.convertAndSend("test_exchange_topic", "info.error.2", "info.error.2");
    amqpTemplate.convertAndSend("test_exchange_topic", "error", "error");
    amqpTemplate.convertAndSend("test_exchange_topic", "error.info", "error.info");
    amqpTemplate.convertAndSend("test_exchange_topic", "error.info.1", "error.info.1");
}
创建多个消费者:
TopicError
@RabbitListener(queues = "test_queue_topic_error")
@Component
public class TopicError {
    @RabbitHandler
    public void process(String message){
        System.out.println("[error] rev : " + message);
    }
}
TopicInfo
@RabbitListener(queues = "test_queue_topic_info")
@Component
public class TopicInfo {
    @RabbitHandler
    public void process(String message){
        System.out.println("[info] rev : " + message);
    }
}
运行项目,调取接口,结果如下:
[error] rev : info.error.2
[info] rev : info.1.2
[error] rev : error
[info] rev : info.error.2
[error] rev : error.info
[error] rev : error.info.1

可以看出 info.error.2 消息被两个消费者都消费了,info.*.*只能匹配3个单词,#则可以匹配0个或多个

12.3 整合延时消息

12.3.1 安装延时插件

打开http://www.rabbitmq.com/community-plugins.html页面, 下载对应RabbitMQ版本的插件

将其放置到RabbitMQ安装目录下的plugins目录下,并使用如下命令启动这个插件:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

关闭命令为:

rabbitmq-plugins disable rabbitmq_delayed_message_exchange

12.3.2 整合代码

RabbitMQConfig类中增加如下代码:

/**
 * 延时消息 注意交换机为CustomExchange类型
 */
private static final String DELAY_EXCHANGE_NAME = "delay_exchange_name";
private static final String DELAY_QUEUE_NAME1 = "delay_queue_name1";
private static final String DELAY_QUEUE_NAME2 = "delay_queue_name2";
@Bean
public CustomExchange delayExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-delayed-type", "direct");
    return new CustomExchange(DELAY_EXCHANGE_NAME, "x-delayed-message",true, false,args);
}
@Bean
public Queue delayQueue1() {
    return new Queue(DELAY_QUEUE_NAME1, true, false, false, null);
}
@Bean
public Binding bindingQueue1() {
    return BindingBuilder.bind(delayQueue1()).to(delayExchange()).with(DELAY_QUEUE_NAME1).noargs();
}
@Bean
public Queue delayQueue2() {
    return new Queue(DELAY_QUEUE_NAME2, true, false, false, null);
}
@Bean
public Binding bindingQueue2() {
    return BindingBuilder.bind(delayQueue2()).to(delayExchange()).with(DELAY_QUEUE_NAME2).noargs();
}
创建生产者:
/**
 * 延时通信
 */
@RequestMapping(value="/delayQueue",method = RequestMethod.GET)
public void daleyQueue() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("消息发送时间:"+sdf.format(new Date()));
    String message="这是信息";
    for (int i = 0; i < 10; i++) {
        long time = i*10000;
        if (i%3==0){
            amqpTemplate.convertAndSend("delay_exchange_name", "delay_queue_name1", message, new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    message.getMessageProperties().setHeader("x-delay",time);
                    return message;
                }
            });
        }else{
            amqpTemplate.convertAndSend("delay_exchange_name", "delay_queue_name2", message, new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    message.getMessageProperties().setHeader("x-delay",time);
                    return message;
                }
            });
        }
    }
}
创建消费者:
DurableKey1
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * Author: 
 * CreateTime:2018/8/20  9:12
 * Desc:
 */
@RabbitListener(queues = "delay_queue_name1")
@Component
public class DurableKey1 {
    @RabbitHandler
    public void process(String message){
        System.out.println("start one work and now data:"+new Date().toString());
        System.out.println("This delay_queue_name1 msg is:"+message);
    }
}
DurableKey2
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * Author: 
 * CreateTime:2018/8/20  9:12
 * Desc:
 */
@RabbitListener(queues = "delay_queue_name1")
@Component
public class DurableKey2 {
    @RabbitHandler
    public void process(String message){
        System.out.println("start one work and now data:"+new Date().toString());
        System.out.println("This delay_queue_name2 msg is:"+message);
    }
}
启动项目,访问接口,结果如下:
消息发送时间:2018-08-21 08:32:11
start one work and now data:Tue Aug 21 08:32:12 CST 2018
This delay_queue_name1 msg is:这是信息
start one work and now data:Tue Aug 21 08:32:22 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:32:32 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:32:42 CST 2018
This delay_queue_name1 msg is:这是信息
start one work and now data:Tue Aug 21 08:32:52 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:33:02 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:33:12 CST 2018
This delay_queue_name1 msg is:这是信息
start one work and now data:Tue Aug 21 08:33:22 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:33:32 CST 2018
This delay_queue_name2 msg is:这是信息
start one work and now data:Tue Aug 21 08:33:42 CST 2018
This delay_queue_name1 msg is:这是信息

 

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值