3.SpringBoot2开发使用篇②

5.整合第三方技术

5.1 缓存

缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间

实验
导入图书管理系统(链接:https://pan.baidu.com/s/1Nb4__BxEg4XiWAmkg3wekQ 提取码:a9zz)

模拟缓存

  1. 修改BookServiceImpl(添加了缓存cache,修改getById方法使用缓存读取数据)
package com.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.BookService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements BookService {

    @Autowired
    private BookDao bookDao;

    //缓存
    private HashMap<Integer,Book> cache = new HashMap<Integer, Book>();

    @Override
    public boolean save(Book book) {
        return bookDao.insert(book) > 0;
    }

    @Override
    public boolean update(Book book) {
        return bookDao.updateById(book) > 0;
    }

    @Override
    public boolean delete(Integer id) {
        return bookDao.deleteById(id) > 0;
    }

    @Override
    public Book getById(Integer id) {
        //如果当前缓存中没有本次要查询的数据,则进行查询,否则直接从缓存中获取数据返回
        Book book = cache.get(id);
        if(book == null){
            Book queryBook = bookDao.selectById(id);
            cache.put(id,queryBook);
            return queryBook;
        }
        return cache.get(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.selectList(null);
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage,pageSize);
        bookDao.selectPage(page,null);
        return page;
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType());
        lqw.like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName());
        lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription());
        IPage page = new Page(currentPage,pageSize);
        bookDao.selectPage(page,lqw);
        return page;
    }
}
  1. 在postman中连续发送get请求:http://localhost/books/3
    发现只有第一次去查询数据库

模拟手机验证

  1. MsgService接口
package com.example.service;

public interface MsgService {
    public String get(String tele);
    public boolean check(String tele,String code);
}
  1. MsgServiceImpl实现类
package com.example.service.impl;

import com.example.service.MsgService;
import org.springframework.stereotype.Service;

import java.util.HashMap;

@Service
public class MsgServiceImpl implements MsgService {
    private HashMap<String,String> cache = new HashMap<String, String>();

    @Override
    public String get(String tele) {
        String code = tele.substring(tele.length() - 6);
        cache.put(tele,code);
        return code;
    }

    @Override
    public boolean check(String tele, String code) {
        String queryCode = cache.get(tele);
        return code.equals(queryCode);
    }
}
  1. MsgController
package com.example.controller;

import com.example.service.MsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/msg")
public class MsgController {

    @Autowired
    private MsgService msgService;

    @GetMapping("{tele}")
    public String get(@PathVariable String tele){
        return msgService.get(tele);
    }

    @PostMapping
    public boolean check(String tele,String code){
        return msgService.check(tele,code);
    }

}

4.postman模拟发送请求
发送get请求:http://localhost/msg/12345678910
获取验证码为678910
发送post请求:http://localhost/msg?tele=12345678910&code=678910
判断验证码是否正确

5.1.1 Spring缓存使用方法
  1. 导入缓存技术对应的starter
<!--cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  1. 启用缓存
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
//开启缓存功能
@EnableCaching
public class SpringbootBooksApplication {

    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SpringbootBooksApplication.class, args);
    }

}
  1. 设置当前操作的结果数据进入缓存
@Override
@Cacheable(value = "cacheSpace",key = "#id")
public Book getById(Integer id) {
    return bookDao.selectById(id);
}
5.1.2 手机验证码案例——生成验证码

SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
Generic、JCache、Ehcache、Hazelcast、Infinispan、Couchbase、Redis、Caffeine、Simple(默认)、memcached

  • 需求
    • 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
    • 输入手机号和验证码验证结果
  • 需求分析
    • 提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据
    • 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果
  1. SMSCode实体类
package com.example.domain;

import lombok.Data;

@Data
public class SMSCode {
    private String tele;
    private String code;
}
  1. CodeUtils工具类,用于生成验证码和读取缓存中的验证码
package com.example.controller.utils;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class CodeUtils {
    private  String[] patch = {"00000","0000","000","00","0",""};
    public String generator(String tele){
        int hash = tele.hashCode();
        int encryption = 20206666;
        long result = hash ^ encryption;
        long nowTime = System.currentTimeMillis();
        result = result ^ nowTime;
        long code = Math.abs(result % 1000000) ;
//        code = 123;
        String codeStr = code + "";

        return patch[codeStr.length() - 1] + codeStr;
    }

    @Cacheable(value = "smsCode",key = "#tele")
    public String get(String tele){
        return null;
    }

//    public static void main(String[] args) {
//        System.out.println(new CodeUtils().generator("18866666666"));
//    }
}
  1. SMSCodeService接口及其实现类
package com.example.service;

import com.example.domain.SMSCode;

public interface SMSCodeService {
    public String sendCodeToSMS(String tele);
    public boolean checkCode(SMSCode smsCode);
}


package com.example.service.impl;

import com.example.controller.utils.CodeUtils;
import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    @Override
    @CachePut(value = "smsCode",key="#tele")
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
        //去除内存中的验证码与传递过来的验证码对比,如果相同,返回true
        String code = smsCode.getCode();
        String cacheCode = codeUtils.get(smsCode.getTele());
        return code.equals(cacheCode);
    }
}
  1. SMSCodeController
package com.example.controller;

import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/msm")
public class SMSCodeController {
    @Autowired
    private SMSCodeService smsCodeService;

    @GetMapping
    public String getCode(String tele){
        String code = smsCodeService.sendCodeToSMS(tele);
        return code;
    }

    @PostMapping
    public boolean checkCode(SMSCode smsCode){
        return smsCodeService.checkCode(smsCode);
    }
}

5.postman模拟发送请求
发送get请求:http://localhost/msm?tele=12345678910
获取验证码:330467
发送post请求:http://localhost/msm?tele=12345678910&code=330467
判断验证码是否正确

5.1.3 变更缓存供应商:Ehcache
  1. 加入Ehcache坐标(缓存供应商实现)
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
  1. 缓存设定为使用Ehcache
spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml
  1. 提供ehcache配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!--默认缓存策略 -->
    <!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
    <!-- diskPersistent:是否启用磁盘持久化-->
    <!-- maxElementsInMemory:最大缓存数量-->
    <!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
    <!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果-->
    <!-- timeToLiveSeconds:最大存活时间-->
    <!-- memoryStoreEvictionPolicy:缓存清除策略-->
    <defaultCache
            eternal="false"
            diskPersistent="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            timeToIdleSeconds="60"
            timeToLiveSeconds="60"
            memoryStoreEvictionPolicy="LRU" />

    <Cache
            name="smsCode"
            eternal="false"
            diskPersistent="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            timeToIdleSeconds="60"
            timeToLiveSeconds="60"
            memoryStoreEvictionPolicy="LRU" />
</ehcache>
5.1.4 变更缓存供应商:redis
  1. 启动redis
redis-server.exe redis.windows.conf
  1. 加入Redis坐标(缓存供应商实现)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置Redis服务器,缓存设定为使用Redis以及设置Redis相关配置
spring:
  cache:
    type: redis
    redis:
      use-key-prefix: true # 是否使用前缀名(系统定义前缀名)
      time-to-live: 10s # 有效时长
      key-prefix: sms_ # 追加自定义前缀名
      cache-null-values: false # 是否允许存储空值
  redis:
    host: localhost
    port: 6379
5.1.5 变更缓存供应商:memcached

下载memcached
地址:https://www.runoob.com/memcached/window-install-memcached.html
百度网盘(链接:https://pan.baidu.com/s/1-M3f8ZlJp0-UJNF69DYaSA 提取码:a4de)

安装memcached
以管理员身份运行cmd

#安装
memcached.exe -d install

#启动
memcached.exe -d start

#停止
memcached.exe -d stop

memcached客户端选择:
Memcached Client for Java:最早期客户端,稳定可靠,用户群广
SpyMemcached:效率更高
Xmemcached:并发处理更好

SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理

  1. 加入Xmemcache坐标(缓存供应商实现)
<dependency>
    <groupId>com.googlecode.xmemcached</groupId>
    <artifactId>xmemcached</artifactId>
    <version>2.4.7</version>
</dependency>
  1. 配置memcached服务器必要属性
memcached:
  servers: localhost:11211 # memcached服务器地址
  poolsize: 10 # 连接池的数量
  opTimeout: 3000 # 设置默认操作超时
  1. 创建读取属性配置信息类,加载配置
package com.example.config;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Data
@ConfigurationProperties(prefix = "memcached")
public class XMemcachedProperties {
    private String servers;
    private int poolsize;
    private long opTimeout;
}
  1. 创建客户端配置类
package com.example.config;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class XMemcachedConfig {
    @Autowired
    private XMemcachedProperties memcachedProperties;

    @Bean
    public MemcachedClient getMemcachedClient() throws IOException {
        XMemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(memcachedProperties.getServers());
        memcachedClientBuilder.setConnectionPoolSize(memcachedProperties.getPoolsize());
        memcachedClientBuilder.setOpTimeout(memcachedProperties.getOpTimeout());
        MemcachedClient memcachedClient = memcachedClientBuilder.build();
        return memcachedClient;
    }
}
  1. 配置memcached属性
package com.example.service.impl;

import com.example.controller.utils.CodeUtils;
import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import net.rubyeye.xmemcached.MemcachedClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    //springboot中使用xmemcached
    @Autowired
    private MemcachedClient memcachedClient;

    @Override
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        try {
            memcachedClient.set(tele,10,code);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
        String code = null;
        try {
            code = memcachedClient.get(smsCode.getTele()).toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return smsCode.getCode().equals(code);
    }
}
5.1.6 变更缓存供应商:jetcache

jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能

  • jetCache设定了本地缓存与远程缓存的多级缓存解决方案
    • 本地缓存(local)
      • LinkedHashMap
      • Caffeine
    • 远程缓存(remote)
      • Redis
      • Tair
  1. 加入jetcache坐标
<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>
  1. 配置远程缓存必要属性
jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50
  1. 启用jetcache
package com.example;

import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class SpringbootBooksApplication {

    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SpringbootBooksApplication.class, args);
    }
}
  1. 使用jetcache
package com.example.service.impl;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import com.example.controller.utils.CodeUtils;
import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    @CreateCache(area = "sms",name = "jetCache",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String,String> jetCache;

    @Override
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}
  1. 配置本地缓存必要属性
jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  1. 使用本地缓存
package com.example.service.impl;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.example.controller.utils.CodeUtils;
import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    @CreateCache(name = "jetCache",expire = 10,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
    private Cache<String,String> jetCache;

    @Override
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

配置范例

jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
      limit: 100
  remote:
    default:
      host: localhost
      port: 6379
      type: redis
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50

方法缓存

  1. 启用方法注解
package com.example;

import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
//开启方法注解缓存
@EnableMethodCache(basePackages = "com.example")
public class SpringbootBooksApplication {

    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SpringbootBooksApplication.class, args);
    }
}

2.使用方法注解操作缓存

package com.example.service.impl;

import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CacheUpdate;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.BookService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public boolean save(Book book) {
        return bookDao.insert(book) > 0;
    }

    @Override
    //修改以后更新缓存
    @CacheUpdate(name = "book_",key = "#book.id",value = "#book")
    public boolean update(Book book) {
        return bookDao.updateById(book) > 0;
    }

    @Override
    //删除以后更新缓存
    @CacheInvalidate(name = "book_",key = "#id")
    public boolean delete(Integer id) {
        return bookDao.deleteById(id) > 0;
    }

    @Override
    //添加缓存
    @Cached(name = "book_",key = "#id",expire = 3600,cacheType = CacheType.REMOTE)
    public Book getById(Integer id) {
        return bookDao.selectById(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.selectList(null);
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage,pageSize);
        bookDao.selectPage(page,null);
        return page;
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize, Book book) {
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType());
        lqw.like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName());
        lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription());
        IPage page = new Page(currentPage,pageSize);
        bookDao.selectPage(page,lqw);
        return page;
    }
}
  1. 缓存对象必须保证可序列化
package com.example.domain;

import lombok.Data;
import java.io.Serializable;

@Data
public class Book implements Serializable {
    private Integer id;
    private String type;
    private String name;
    private String description;
}
  1. 修改配置
jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      keyConvertor: fastjson
      valueEncode: java
      valueDecode: java
      poolConfig:
        maxTotal: 50
  1. 查看缓存统计报告
jetcache:
  statIntervalMinutes: 15
5.1.7 变更缓存供应商:j2cache

j2cache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能

基于 ehcache + redis 进行整合

  1. 加入j2cache坐标,加入整合缓存的坐标(slf4j冲突,需要排除)
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-core</artifactId>
    <version>2.8.4-release</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-spring-boot2-starter</artifactId>
    <version>2.8.0-release</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
  1. 配置使用j2cache(application.yml)
j2cache:
  config-location: j2cache.properties
  1. 配置一级缓存与二级缓存以及一级缓存数据到二级缓存的发送方式(j2cache.properties)
# 1级缓存
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml

# 设置是否启用2级缓存
j2cache.L2-cache-open = true

# 2级缓存
j2cache.L2.provider_class =net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379

# 1级缓存中的数据如何到达2级缓存
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
  1. 设置使用缓存
package com.example.service.impl;

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.example.controller.utils.CodeUtils;
import com.example.domain.SMSCode;
import com.example.service.SMSCodeService;
import net.oschina.j2cache.CacheChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @Autowired
    private CodeUtils codeUtils;

    @Autowired
    private CacheChannel cacheChannel;

    @Override
    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        cacheChannel.set("sms",tele,code);
        return code;
    }

    @Override
    public boolean checkCode(SMSCode smsCode) {
        String code = cacheChannel.get("sms",smsCode.getTele()).asString();
        return smsCode.getCode().equals(code);
    }
}

5.2 任务

定时任务时企业应用中的常见操作
年度报表、缓存统计报告等

java中的定时任务

package com.example;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTaskApp {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("time task run...");
            }
        };
        timer.schedule(task,0,2000);
    }
}

市面上流行的定时任务技术
Quartz
Spring Task

5.2.1 springboot整合quartz

相关概念:
1.工作(Job):用于定义具体执行的工作
2.工作明细(JobDetail):用于描述定时工作相关的信息
3.触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
4.调度器(Scheduler):描述了工作明细与触发器的对应关系

实验
创建新的springboot项目

  1. 导入SpringBoot整合quartz的坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 定义具体要执行的任务,继承QuartzJobBean
package com.example.quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class MyQuartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("quartz task run ...");
    }
}
  1. 定义工作明细与触发器,并绑定对应关系
package com.example.config;

import com.example.quartz.MyQuartz;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail printJobDetail(){
        //绑定具体的工作
        return JobBuilder
                .newJob(MyQuartz.class)
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger printJobTrigger(){
        //绑定对应的工作明细
        ScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        return TriggerBuilder
                .newTrigger()
                .forJob(printJobDetail())
                .withSchedule(schedBuilder)
                .build();
    }
}
  1. 运行App程序
5.2.1 SpringBoot整合task
  1. 开启定时任务功能
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
//开启定时任务功能
@EnableScheduling
public class SpringbootTaskApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootTaskApplication.class, args);
    }

}
  1. 设置定时执行的任务,并设定执行周期
package com.example.quartz;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MyBean {
    @Scheduled(cron = "0/5 * * * * ?")
    public void print(){
        System.out.println("spring task run ...");
    }
}

定时任务相关操作

spring:
  task:
    scheduling:
      # 任务调度线程池大小 默认 1
      pool:
        size: 1
      # 调度线程名称前缀 默认 scheduling-
      thread-name-prefix: ssm_
      shutdown:
        # 线程池关闭时等待所有任务完成
        await-termination: false
        # 调度线程关闭前最大等待时间,确保最后一定关闭
        await-termination-period: 10s

5.3 邮件

5.3.1 SpringBoot整合JavaMail

SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于发送电子邮件的传输协议
POP3(Post Office Protocol - Version 3):用于接收电子邮件的标准协议
IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议

创建新的springboot(2.5.4)项目

  1. 导入SpringBoot整合JavaMail的坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 配置JavaMail
    邮箱改成自己的,密码是在邮箱中开启邮件传输服务获得的
spring:
  mail:
    host: smtp.qq.com
    username: ******@qq.com
    password: ******
  1. 邮件发送功能(发送邮件的接口和实现类)
    SendMailServiceImpl中修改发送人和接收人邮箱
package com.example.service;

public interface SendMailService {
    void sendMail();
}


package com.example.service.impl;

import com.example.service.SendMailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class SendMailServiceImpl implements SendMailService {
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "******@qq.com";
    //接收人
    private  String to = "*******@163.com";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "测试邮件正文内容";

    @Override
    public void sendMail() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(context);
        javaMailSender.send(message);
    }
}
  1. 发送邮件
package com.example;

import com.example.service.SendMailService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootMailApplicationTests {
    @Autowired
    private SendMailService sendMailService;

    @Test
    void contextLoads() {
        sendMailService.sendMail();
    }

}
  1. 发送复杂邮件
package com.example.service.impl;

import com.example.service.SendMailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

@Service
public class SendMailServiceImpl implements SendMailService {
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "******@qq.com";
    //接收人
    private  String to = "******@163.com";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "<a href='www.baidu.com'>点开有惊喜</a>\n" +
            "< img src ='https://i0.hdslb.com/bfs/article/6edc099f2a925e4cad4685bbbcf8823a1f7aaccc.jpg@942w_531h_progressive.webp'>";

    @Override
    public void sendMail() {
        MimeMessage message = null;
        try {
            message = javaMailSender.createMimeMessage();
//            MimeMessageHelper helper = new MimeMessageHelper(message);
            //发送附件则加上true
            MimeMessageHelper helper = new MimeMessageHelper(message,true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(context,true);
            //添加附件
            File f1 = new File("D:\\tools\\memcached-win64-1.4.4-14.zip");
            helper.addAttachment(f1.getName(),f1);

            javaMailSender.send(message);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}

5.4 消息

消息发送方:生产者
消息接收方:消费者

同步消息
异步消息

企业级应用中广泛使用的三种异步消息传递技术:JMS、AMQP、MQTT

JMS

  • JMS(Java Message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
  • JMS消息模型
    • peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
    • publish-subscribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
  • JMS消息种类
    • TextMessage
    • MapMessage
    • BytesMessage
    • StreamMessage
    • ObjectMessage
    • Message (只有消息头和属性)
  • JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范)

AMQP

  • AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
  • 优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
  • AMQP消息模型
    • direct exchange
    • fanout exchange
    • topic exchange
    • headers exchange
    • system exchange
  • AMQP消息种类:byte[]
  • AMQP实现:RabbitMQ、StormMQ、RocketMQ

MQTT
MQTT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一

Kafka
Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。

5.4.1 购物订单案例——发送短信

新建springboot项目,勾选web

  1. 修改配置文件(方便访问)
server:
  port: 80
  1. OrderService接口和实现类(处理消息)
package com.example.service;

public interface OrderService {
    void order(String id);
}


package com.example.service.impl;

import com.example.service.MessageService;
import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private MessageService messageService;

    @Override
    public void order(String id) {
        //一系列操作,包含各种服务调用,处理各种业务
        System.out.println("订单处理开始");
        //短信消息处理
        messageService.sendMessage(id);
        System.out.println("订单处理结束");
    }
}
  1. MessageService接口和实现类(将消息添加到消息队列中)
package com.example.service;

public interface MessageService {
    void sendMessage(String id);
    String doMessage();
}


package com.example.service.impl;

import com.example.service.MessageService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
public class MessageServiceImpl implements MessageService {
    private ArrayList<String> msgList = new ArrayList<>();

    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列,id:" + id);
        msgList.add(id);
    }

    @Override
    public String doMessage() {
        String id = msgList.remove(0);
        System.out.println("已完成短信发送服务,id:" + id);
        return id;
    }
}
  1. OrderController(接受需要处理的消息)
package com.example.controller;

import com.example.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("{id}")
    public void order(@PathVariable String id){
        orderService.order(id);
    }
}
  1. MessageController(处理消息)
package com.example.controller;

import com.example.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/msgs")
public class MessageController {
    @Autowired
    private MessageService messageService;

    @GetMapping
    public String doMessage(){
        String id = messageService.doMessage();
        return id;
    }
}
  1. 测试
    使用postman模拟
    发送多个post请求
    http://localhost/orders/1
    http://localhost/orders/aa
    http://localhost/orders/2

处理消息
发送多个get请求
http://localhost/msgs

5.4.1 SpringBoot整合ActiveMQ

下载地址:https://activemq.apache.org/components/classic/download/
(链接:https://pan.baidu.com/s/1LVviNix5s4D_OC8S4FMjKQ 提取码:uatp)

安装:解压缩

开启:双击打开bin\win64目录下的wrapper.exe

访问服务器:
http://127.0.0.1:8161/
用户名&密码:admin

  1. 导入SpringBoot整合ActiveMQ坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
  1. 配置ActiveMQ(采用默认配置)
server:
  port: 80
spring:
  activemq:
    broker-url: tcp://localhost:61616
  # 使用发布订阅模型
#  jms:
#    pub-sub-domain: true
  1. 生产与消费消息(使用默认消息存储队列)
    注释MessageServiceImpl的@Service注解防止冲突
package com.example.service.impl.activemq;

import com.example.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceActivemqImpl implements MessageService {
    @Autowired
    private JmsMessagingTemplate messagingTemplate;
    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列,id:" + id);
        messagingTemplate.convertAndSend("order.queue.id",id);
    }

    @Override
    public String doMessage() {
        String id = messagingTemplate.receiveAndConvert("order.queue.id",String.class);
        System.out.println("已完成短信发送服务,id:" + id);
        return id;
    }
}
  1. 使用消息监听器对消息队列监听
package com.example.service.impl.activemq.listener;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Component;

@Component
public class MessageListener {
    @JmsListener(destination = "order.queue.id")
    //流程性业务消息消费完转入下一个消息队列
    @SendTo("order.other.queue.id")
    public String receive(String id){
        System.out.println("已完成短信发送业务,id:" + id);
        return "new:" + id;
    }
}
  1. 测试
    发送多个post请求:http://localhost/orders/b

可以在http://127.0.0.1:8161/admin/queues.jsp中查看

5.4.1 SpringBoot整合RabbitMQ

RabbitMQ基于Erlang语言编写,需要安装Erlang

Erlang
下载地址:https://www.erlang.org/downloads
安装:一键傻瓜式安装,安装完毕需要重启,需要依赖Windows组件
环境变量配置
ERLANG_HOME
PATH

RabbitMQ
下载地址:https://rabbitmq.com/install-windows.html
安装:一键傻瓜式安装

安装以后最好重启电脑

在rabbitmq_server-3.9.13\sbin目录下,并且使用管理员权限启动cmd
启动服务

rabbitmq-service.bat start

关闭服务

rabbitmq-service.bat stop

查看服务状态

rabbitmqctl status

服务管理可视化(插件形式)
查看已安装的插件列表

rabbitmq-plugins.bat list

开启服务管理插件

rabbitmq-plugins.bat enable rabbitmq_management

访问服务器
http://localhost:15672
服务端口:5672,管理后台端口:15672
用户名&密码:guest

实验
direct模式

  1. 导入SpringBoot整合RabbitMQ坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 配置RabbitMQ (采用默认配置)
spring:
  rabbitmq:
    host: localhost
    port: 5672

3.注释上一节中类的注解,避免冲突
MessageListener的@Component
MessageServiceActivemqImpl的@Service

  1. 定义消息队列(direct)
package com.example.service.impl.rabbitmq.direct.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfigDirect {

    @Bean
    public Queue directQueue(){
        return new Queue("direct_queue");
    }

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("directExchange");
    }

    @Bean
    public Binding bindingDirect(){
        return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
    }
}
  1. 生产与消费消息(direct)
package com.example.service.impl.rabbitmq.direct;

import com.example.service.MessageService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列(rabbitmq direct),id:" + id);
        amqpTemplate.convertAndSend("directExchange","direct",id);
    }

    @Override
    public String doMessage() {
        return null;
    }
}
  1. 使用消息监听器对消息队列监听(direct)
package com.example.service.impl.rabbitmq.direct.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageListener {
    @RabbitListener(queues = "direct_queue")
    public void receive(String id){
        System.out.println("已完成短信发送业务(rabbitmq direct),id:" + id);
    }
}
  1. 测试,发送多此post请求:http://localhost/orders/bbc

topic模式

  1. 注释上一个实验的类中的注解
  2. 定义消息队列(topic)
package com.example.service.impl.rabbitmq.tipic.config;


import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfigTopic {

    @Bean
    public Queue topicQueue(){
        return new Queue("topic_queue1");
    }

    @Bean
    public Queue topicQueue2(){
        return new Queue("topic_queue2");
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("topicExchange");
    }

    @Bean
    public Binding bindingTopic(){
        return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.id");
    }

    @Bean
    public Binding bindingTopic2(){
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
    }
}

绑定键匹配规则

  • (星号): 用来表示一个单词 ,且该单词是必须出现的

(井号): 用来表示任意数量

  1. 生产与消费消息(topic)
package com.example.service.impl.rabbitmq.tipic;

import com.example.service.MessageService;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceRabbitmqTopicImpl implements MessageService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列(rabbitmq topic),id:" + id);
        amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
    }

    @Override
    public String doMessage() {
        return null;
    }
}
  1. 使用消息监听器对消息队列监听(topic)
package com.example.service.impl.rabbitmq.tipic.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageTopicListener {
    @RabbitListener(queues = "topic_queue1")
    public void receive(String id){
        System.out.println("已完成短信发送业务(rabbitmq topic),id:" + id);
    }

    @RabbitListener(queues = "topic_queue2")
    public void receive2(String id){
        System.out.println("已完成短信发送业务(rabbitmq topic 2),id:" + id);
    }
}
  1. 测试,发送多此post请求:http://localhost/orders/bbc
5.4.1 SpringBoot整合RocketMQ

下载地址:https://rocketmq.apache.org/
安装:解压缩

默认服务端口:9876

环境变量配置
ROCKETMQ_HOME
PATH
NAMESRV_ADDR (建议): 127.0.0.1:9876

启动命名服务器:
双击bin目录下的mqnamesrv.cmd
注意:需要配置上述环境变量,并且JAVA_HOME的路径不能有空格,否则启动失败

启动broker:
双击bin目录下的mqbroker.cmd

服务器功能测试:生产者

tools org.apache.rocketmq.example.quickstart.Producer

服务器功能测试:消费者

tools org.apache.rocketmq.example.quickstart.Consumer

整合

  1. 导入SpringBoot整合RocketMQ坐标
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>
  1. 配置RocketMQ (采用默认配置)
rocketmq:
  name-server: localhost:9876
  producer:
    group: group_rocketmq
  1. 生产消息
package com.example.service.impl.rocketmq;

import com.example.service.MessageService;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceRocketmaImpl implements MessageService {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列(rocketmq),id:" + id);
        //同步
//        rocketMQTemplate.convertAndSend("order_id",id);
        //异步
        SendCallback callback = new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("消息发送成功");
            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println("消息发送失败");
            }
        };
        rocketMQTemplate.asyncSend("order_id",id,callback);
    }

    @Override
    public String doMessage() {
        return null;
    }
}
  1. 使用消息监听器对消息队列监听
package com.example.service.impl.rocketmq.listener;

import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

@Component
@RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")
public class MessqgeListener implements RocketMQListener<String> {
    @Override
    public void onMessage(String id) {
        System.out.println("已完成短信发送业务(rocketmq),id:" + id);
    }
}
5.4.1 SpringBoot整合Kafka

下载地址:https://kafka.apache.org/downloads
windows 系统下3.0.0版本存在BUG,建议使用2.X版本

安装:解压缩

启动zookeeper
运行文件在bin\windows下

zookeeper-server-start.bat ../../config/zookeeper.properties

默认端口:2181

启动kafka

kafka-server-start.bat ..\..\config\server.properties

默认端口:9092

创建topic

kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test

查看topic

kafka-topics.bat --zookeeper 127.0.0.1:2181 --list

删除topic

kafka-topics.bat --delete --zookeeper localhost:2181 --topic test

生产者功能测试

kafka-console-producer.bat --broker-list localhost:9092 --topic test

消费者功能测试

kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning

整合
1.导入SpringBoot整合Kafka坐标

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
  1. 配置Kafka (采用默认配置)
spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: order
  1. 生产消息
package com.example.service.impl.kafka;

import com.example.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class MessageServiceKafkaImpl implements MessageService {

    @Autowired
    private KafkaTemplate kafkaTemplate;

    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列(kafka),id:" + id);
        kafkaTemplate.send("test",id);
    }

    @Override
    public String doMessage() {
        return null;
    }
}
  1. 使用消息监听器对消息队列监听
package com.example.service.impl.kafka.listener;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class MessageListener {
    @KafkaListener(topics = "test")
    private void onMessage(ConsumerRecord<String,String> record){
        System.out.println("已完成短信发送业务(kafka),id:" + record.value());
    }
}

6.监控

6.1 监控的意义

监控的意义
监控服务状态是否宕机
监控服务运行指标(内存、虚拟机、线程、请求等)
监控日志
管理服务(服务下线)

监控的实施方式
显示监控信息的服务器:用于获取服务信息,并显示对应的信息
运行的服务:启动时主动上报,告知监控服务器自己需要受到监控

6.2 可视化监控平台

Spring Boot Admin,开源社区项目,用于管理和监控SpringBoot应用程序。 客户端注册到服务端后,通过HTTP请求方式,服务端定期从客户端获取对应的信息,并通过UI界面展示对应信息。

Admin服务端

  1. 创建新的springboot(2.5.4)项目,勾选Ops中的Codecentric’s Spring Boot Admin(Server)
    要求spring-boot.version和spring-boot-admin.version对应

或添加依赖坐标

<properties>
    <spring-boot-admin.version>2.5.4</spring-boot-admin.version>
</properties>
<dependencies>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-dependencies</artifactId>
            <version>${spring-boot-admin.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  1. 配置访问端口
server:
  port: 8080
  1. 设置启用Spring-Admin
package com.example;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//启用SpringAdmin
@EnableAdminServer
public class SpringbootAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootAdminApplication.class, args);
    }

}
  1. 启动后,访问http://localhost:8080/查看

Admin客户端

  1. 创建新的springboot(2.5.4)项目,勾选Ops中的Codecentric’s Spring Boot Admin(Client),以及Web
    要求spring-boot.version和spring-boot-admin.version对应

或添加依赖坐标

<properties>
    <spring-boot-admin.version>2.5.4</spring-boot-admin.version>
</properties>
<dependencies>
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-dependencies</artifactId>
            <version>${spring-boot-admin.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  1. 配置参数(服务端地址,允许监控的资源)
spring:
  boot:
    admin:
      client:
        url: http://localhost:8080
server:
  port: 80
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"

  1. 在http://localhost:8080/查看监控信息

6.3 监控原理

Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
访问当前应用所有端点信息:/actuator
访问端点详细信息:/actuator/端点名称

端点表
ID描述默认启用
auditevents暴露当前应用程序的审计事件信息。
beans显示应用程序中所有 Spring bean 的完整列表。
caches暴露可用的缓存。
conditions显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties的校对清单。
env暴露 Spring ConfigurableEnvironment中的属性。
flyway显示已应用的 Flyway 数据库迁移。
health显示应用程序健康信息
httptrace显示 HTTP 追踪信息(默认情况下,最后 100 个 HTTP 请求/响应交换)。
info显示应用程序信息。
integrationgraph显示 Spring Integration 图。
loggers显示和修改应用程序中日志记录器的配置
liquibase显示已应用的 Liquibase 数据库迁移。
metrics显示当前应用程序的指标度量信息。
mappings显示所有@RequestMapping路径的整理清单。
scheduledtasks显示应用程序中的调度任务。
sessions允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。
shutdown正常关闭应用程序。
threaddump执行线程 dump。
heapdump返回一个 hprof 堆 dump 文件。
jolokia通过 HTTP 暴露 JMX bean(当 Jolokia 在 classpath 上时,不适用于 WebFlux)。
logfile返回日志文件的内容(如果已设置 logging.file 或 logging.path 属性)。支持使用 HTTP Range 头来检索部分日志文件的内容。
prometheus以可以由 Prometheus 服务器抓取的格式暴露指标。

启用指定端点

management:
  endpoint:
    health:   # 端点名称
      enabled: true
      show-details: always
  beans:    # 端点名称
    enabled: true

启用所有端点

management:
  endpoints:
    enabled-by-default: true

暴露端点功能
端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息

management.endpoints.jmx.exposure.exclude
management.endpoints.jmx.exposure.include 默认:*
management.endpoints.web.exposure.exclude
management.endpoints.web.exposure.include 默认:info,health

6.4 自定义监控指标

为info端点添加自定义指标
方式一:在配置文件中添加

info:
  appName: @project.artifactId@
  version: @project.version@
  author: hutc_alen

方式二:编写类添加

package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class InfoConfig implements InfoContributor {
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("runTime",System.currentTimeMillis());
        Map infoMap = new HashMap();
        infoMap.put("buildTime","2022");
        builder.withDetails(infoMap);
    }
}

可以在http://localhost:8080/对应项目的Details中的info查看

为Health端点添加自定义指标

package com.example.actuator;

import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class HealthConfig extends AbstractHealthIndicator {
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        boolean condition = true;
        if(condition) {
            builder.withDetail("runTime", System.currentTimeMillis());
            Map infoMap = new HashMap();
            infoMap.put("buildTime", "2022");
            builder.withDetails(infoMap);
//            builder.up();
            builder.status(Status.UP);
        }else{
            builder.withDetail("上线了嘛?","你做梦");
//            builder.down();
            builder.status(Status.DOWN);
        }
    }
}

为Metrics端点添加自定义指标

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements BookService {
    private Counter counter;
    public BookServiceImpl(MeterRegistry meterRegistry){
        counter = meterRegistry.counter("用户付费操作次数:");
    }
    @Autowired
    private BookDao bookDao;

    @Override
    public boolean save(Book book) {
        counter.increment();
        return bookDao.insert(book) > 0;
    }
    ...
}

自定义端点

package com.example.actuator;

import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
@Endpoint(id = "pay",enableByDefault = true)
public class PayEndpoint {
    @ReadOperation
    public Object getPay(){
        Map payMap = new HashMap();
        payMap.put("level 1","300");
        payMap.put("level 2","152");
        payMap.put("level 3","333");
        return payMap;
    }
}

发送get请求:http://localhost/actuator/pay
获取信息

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值