SpringBoot的高级教程

  • SpringBoot缓存
  • Spring的缓存抽象
  • 1、基本概念
  • 2、整合项目
  • 3、缓存原理
  • 4、Cache的注解
  • 1、@Cacheput
  • 2、CacheEvict
  • 3、Caching
  • 5、Redis
  • 二、SpringBoot的消息中间件
  • 1、JMS&AMQP简介
  • 概述
  • 2、RabbitMQ简介
  • 1、核心概念
  • 2、RabbitMQ的运行机制
  • 3、RabbitMQ安装测试
  • 4、创建工程整合
  • 1、RabbitTemplate
  • 2、开启基于注解的方式
  • 3、AmqpAdmin
  • SpringBoot的任务
  • 1. 异步任务
  • 2.邮件任务
  • 配置
  • 自写工具类
  • 3. 定时任务
  • 其他
  • 应用瘦身


SpringBoot缓存

缓存的场景

  • 临时性数据存储【校验码】
  • 避免频繁因为相同的内容查询数据库

Spring的缓存抽象

包括一些JSR107的注解

1、基本概念

重要的概念&缓存注解

功能

Cache

缓存接口,定义缓存操作,实现有:RedisCache、EhCacheCache、ConcurrentMapCache等

CacheManager

缓存管理器,管理各种缓存(Cache)组件

@Cacheable

针对方法配置,根据方法的请求参数对其结果进行缓存;缓存时间内再次调用将直接返回缓存结果,不会再执行一次方法

@CacheEvict(value, key)

方法被调用时清空缓存,相当于缓存使用的是写模式中的失效模式。

@CachePut

方法会被调用,结果写入缓存,一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。

@EnableCaching

开启基于注解的缓存

KeyGenerator

缓存数据时key生成的策略

serialize

缓存数据时value序列化策略

2、整合项目

1、新建一个SpringBoot+web+mysql+mybatis+cache项目

2、编写配置文件,连接Mysql

mybatis.configuration.map-underscore-to-camel-case=true

3、创建一个bean实例

public class Department {
    private Integer id;
    private String deptName;

	//setters and getters...
}

Employee

public class Employee {
    private Integer id;
    private String lastName;
    private String gender;
    private String email;
    private Integer dId;
	
	//setters and getters

}

4、创建mapper接口映射数据库,并访问数据库中的数据

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface EmployeeMapper {

    @Select("SELECT * FROM employee WHERE id = #{id}")
    public Employee getEmpById(Integer id);
    @Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
    public void updateEmp(Employee employee);
}

5、主程序入口类添加使用@EnableCaching开启缓存

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

@EnableCaching
@MapperScan("com.table.cache.mapper")
@SpringBootApplication
public class Springboot01CacheApplication {

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

6、编写service,来具体实现mapper中的方法

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * 将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法
     * CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字;
     *
     * 属性:
     *  CacheName/value:指定存储缓存组件的名字
     *  key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
     *  编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
     *  keyGenerator:key的生成器,自己可以指定key的生成器的组件id
     *  key/keyGendertor二选一使用
     *
     *  cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
     *  condition:指定符合条件的情况下,才缓存;
     *  unless:否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以根据返回值判断是否缓存
     * 但是为了避免缓存穿透,空值结果最好也要缓存,此处只是示例
     *  sync:是否使用异步模式,unless不支持
     */
    @Cacheable(cacheNames = {"emp"},key = "#id",condition = "#id>0",unless = "#result==null")
    public Employee getEmp(Integer id){
        System.out.println("查询id= "+id+"的员工");
        return employeeMapper.getEmpById(id);
    }
}

7、编写controller测试

8、测试结果

继续访问,就不会执行方法,因为直接在缓存中取值

3、缓存原理

默认情况下,@EnableCaching 将注册一个ConcurrentMapCacheManager的Bean。ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例在中。

原理:

1、CacheAutoConfiguration

2、导入缓存组件

3、查看哪个缓存配置生效

SimpleCacheConfiguration生效

4、给容器注册一个CacheManager:ConcurrentMapCacheManager

5、可以获取和创建ConcurrentMapCache,作用是将数据保存在ConcurrentMap中

运行流程

1、方法运行之前,先查Cache(缓存组件),按照cacheName的指定名字获取;

(CacheManager先获取相应的缓存),第一次获取缓存如果没有cache组件会自己创建

2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;

key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key

没有参数 key=new SimpleKey()

如果有一个参数 key=参数值

如果多个参数 key=new SimpleKey(params);

3、没有查到缓存就调用目标方法

4、将目标方法返回的结果,放回缓存中

方法执行之前,@Cacheable先来检查缓存中是否有数据,按照参数的值作为key去查询缓存,如果没有,就运行方法,存入缓存,如果有数据,就取出map的值。

4、Cache的注解

1、@Cacheput

修改数据库的某个数据,同时更新缓存

运行时机

先运行方法,再将目标结果缓存起来

cacheable的key是不能使用result的参数的

1、编写更新方法

@CachePut(value = {"emp"},key = "#result.id")
public Employee updateEmp(Employee employee){
    System.out.println("updateEmp"+employee);
    employeeMapper.updateEmp(employee);
    return employee;
}

2、编写Controller方法

@GetMapping("/emp")
public Employee updateEmp(Employee employee){
    employeeService.updateEmp(employee);
    return employee;
}

测试

测试步骤

1、先查询1号员工

2、更新1号员工数据

3、查询1号员工

可能并没有更新,

是因为查询和更新的key不同

效果:

  • 第一次查询:查询mysql
  • 第二次更新:更新mysql
  • 第三次查询:调用内存
2、CacheEvict

清除缓存

编写测试方法

@CacheEvict(value = "emp",key = "#id")
public  void  deleteEmp(Integer id){
    System.out.println("delete的id"+id);
}

allEntries = true,代表不论清除那个key,都重新刷新缓存

beforeInvocation=true.方法执行前,清空缓存,默认是false,如果程序异常,就不会清除缓存

3、Caching

组合

  • Cacheable
  • CachePut
  • CacheEvict

CacheConfig抽取缓存的公共配置

@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {

然后下面的value=emp就不用写了

@Caching(
        cacheable = {
                @Cacheable(value = "emp",key = "#lastName")
        },
        put = {
                @CachePut(value = "emp",key = "#result.id"),
                @CachePut(value = "emp",key = "#result.gender")
        }
)
public Employee getEmpByLastName(String lastName){
    return employeeMapper.getEmpByLastName(lastName);
}

如果查完lastName,再查的id是刚才的值,就会直接从缓存中获取数据

5、Redis

使用Redis作为缓存中间件
配置Redis缓存管理器

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(redisCacheConfiguration)
            .build();

        return redisCacheManager;
    }
}

我们可以使用@Cacheable、@CachePut 或@CacheEvict 注解来操作缓存了。

二、SpringBoot的消息中间件

1、JMS&AMQP简介

1、异步处理

同步机制

并发机制

消息队列机制

2、应用解耦

使用中间件,将两个服务解耦,一个写入,一个订阅

3、流量削锋

例如消息队列的FIFO,限定元素的长度,防止出现多次请求导致的误操作

概述

1、大多数应用,可以通过消息服务中间件来提升系统的异步通信、拓展解耦能力

2、消息服务中的两个重要概念:

消息代理(message broker)和目的地(destination),当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定的目的地。

3、消息队列主要的两种形式的目的地

1)、队列(queue):点对点消息通信【point-to-point】,取出一个没一个,一个发布,多个消费

2)、主题(topic):发布(publish)/订阅(subscribe)消息通信,多人【订阅者】可以同时接到消息

4、JMS(Java Message Service) Java消息服务:

  • 基于JVM消息规范的代理。ActiveMQ/HornetMQ是JMS的实现

5、AMQP(Advanced Message Queuing Protocol)

  • 高级消息队列协议,也是一个消息代理的规范,兼容JMS
  • RabbitMQ是AMQP的实现

JMS

AMQP

定义

Java API

网络线级协议

跨平台



跨语言



Model

(1)、Peer-2-Peer

(2)、Pub/Sub

(1)、direct exchange

(2)、fanout exchange

(3)、topic change

(4)、headers exchange

(5)、system exchange

后四种都是pub/sub ,差别路由机制做了更详细的划分

支持消息类型

TextMessage

MapMessage

ByteMessage

StreamMessage

ObjectMessage

Message

byte[]通常需要序列化

6、SpringBoot的支持

spring-jms提供了对JMS的支持

spring-rabbit提供了对AMQP的支持

需要创建ConnectionFactory的实现来连接消息代理

提供JmsTemplate,RabbitTemplate来发送消息

@JmsListener(JMS).@RabbitListener(AMQP)注解在方法上的监听消息代理发布的消息

@EnableJms,@EnableRabbit开启支持

7、SpringBoot的自动配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

2、RabbitMQ简介

AMQP的实现

1、核心概念

Message:消息头和消息体组成,消息体是不透明的,而消息头上则是由一系列的可选属性组成,属性:路由键【routing-key】,优先级【priority】,指出消息可能需要持久性存储【delivery-mode】

Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序

Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列

Exchange的4中类型:direct【默认】点对点,fanout,topic和headers, 发布订阅,不同类型的Exchange转发消息的策略有所区别

Queue:消息队列,用来保存消息直到发送给消费者,它是消息的容器,也是消息的终点,一个消息可投入一个或多个队列,消息一直在队列里面,等待消费者连接到这个队列将数据取走。

Binding:绑定,队列和交换机之间的关联,多对多关系

Connection:网络连接,例如TCP连接

Channel:信道,多路复用连接中的一条独立的双向数据流通道,信道是建立在真是的TCP链接之内的虚拟连接AMQP命令都是通过信道发送出去的。不管是发布消息,订阅队列还是接受消息,都是信道,减少TCP的开销,复用一条TCP连接。

Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端的 应用程序

VirtualHost:小型的rabbitMQ,相互隔离

Broker:表示消息队列 服务实体

2、RabbitMQ的运行机制

Exchange的三种方式

direct:根据路由键直接匹配,一对一
fanout:不经过路由键,直接发送到每一个队列

topic:类似模糊匹配的根据路由键,来分配绑定的队列

3、RabbitMQ安装测试

1、打开虚拟机,在docker中安装RabbitMQ

#1.安装rabbitmq,使用镜像加速
docker pull registry.docker-cn.com/library/rabbitmq:3-management
[root@node1 ~]# docker images
REPOSITORY                                     TAG                 IMAGE ID            CREATED             SIZE
registry.docker-cn.com/library/rabbitmq        3-management        c51d1c73d028        11 days ago         149 MB
#2.运行rabbitmq
##### 端口:5672 客户端和rabbitmq通信 15672:管理界面的web页面

docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq c51d1c73d028

#3.查看运行
docker ps

2、打开网页客户端并登陆,账号【guest】,密码【guest】,登陆

3、添加 【direct】【faout】【topic】的绑定关系等

1)、添加Exchange,分别添加exchange.directexchange.fanoutexchange.topic

2)、添加 Queues,分别添加lxy.news、wdjr、wdjr.emps、wdjr.news

3)、点击【exchange.direct】添加绑定规则

4)、点击【exchange.fanout】添加绑定规则

5)、点击【exchange.topic】添加绑定规则

/*: 代表匹配1个单词

/#:代表匹配0个或者多个单词

4、发布信息测试

【direct】发布命令,点击 Publish message

查看队列的数量

点击查看发送的信息

【fanout】的发布消息

队列信息

随意一个数据信息例如:wdjr.emp
【topic】发布信息测试

队列的值

信息查看

4、创建工程整合
1、RabbitAutoConfiguration
2、自动配置了连接工厂 ConnectionFactory
3、RabbitProperties封装了 RabbitMQ
4、RabbitTemplate:给RabbitMQ发送和接受消息的
5、AmqpAdmin:RabbitMQ的系统管理功能组件
1、RabbitTemplate

1、新建SpringBoot工程,SpringBoot1.5+Integeration/RabbitMQ+Web

2、RabbitAutoConfiguration文件

3、编写配置文件application.yml

spring:
  rabbitmq:
    host: 192.168.179.131
    port: 5672
    username: guest
    password: guest

4、编写测试类,将HashMap写入Queue

@Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void contextLoads() {
        //Message需要自己构建一个;定义消息体内容和消息头
        // rabbitTemplate.send(exchange, routingKey, message);
        //Object 默认当成消息体,只需要传入要发送的对象,自动化序列发送给rabbitmq;
        Map<String,Object> map = new HashMap<>();
        map.put("msg", "这是第一个信息");
        map.put("data", Arrays.asList("helloWorld",123,true));
        //对象被默认序列以后发送出去
        rabbitTemplate.convertAndSend("exchange.direct","wdjr.news",map);
    }

5、查看网页的信息

6、取出队列的值

取出队列中数据就没了

@Test
public void reciverAndConvert(){

    Object o = rabbitTemplate.receiveAndConvert("wdjr.news");
    System.out.println(o.getClass());
    System.out.println(o);

}

结果

class java.util.HashMap
{msg=这是第一个信息, data=[helloWorld, 123, true]}

7、使用Json方式传递,并传入对象Book

1)、MyAMQPConfig

@Configuration
public class MyAMQPConfig  {

    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

2)、编写Book实体类

package com.wdjr.amqp.bean;

public class Book {
    private String  bookName;
    private String author;

    public Book(){

    }

    public Book(String bookName, String author) {
        this.bookName = bookName;
        this.author = author;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

3)、测试类

@Test
public void contextLoads() {
    //对象被默认序列以后发送出去
    rabbitTemplate.convertAndSend("exchange.direct","wdjr.news",new Book("百年孤独", "季羡林"));
}

4)、查看wdjr.news

5)、取出数据

@Test
public void reciverAndConvert(){
    Object o = rabbitTemplate.receiveAndConvert("wdjr.news");
    System.out.println(o.getClass());
    System.out.println(o);
}

6)、结果演示

class com.wdjr.amqp.bean.Book
Book{bookName='百年孤独', author='季羡林'}
2、开启基于注解的方式

1、新建一个BookService

@Service
public class BookService {
    @RabbitListener(queues = "wdjr.news")
    public void receive(Book book){
        System.out.println(book);
    }

    @RabbitListener(queues = "wdjr")
    public void receive02(Message message){
        System.out.println(message.getBody());
        System.out.println(message.getMessageProperties());
    }
}

2、主程序开启RabbitMQ的注解

@EnableRabbit //开启基于注解的rabbitmq
@SpringBootApplication
public class AmqpApplication {

    public static void main(String[] args) {
        SpringApplication.run(AmqpApplication.class, args);
    }
}
3、AmqpAdmin

创建和删除 Exchange 、Queue、Bind

1)、创建Exchange

@Test
public void createExchange(){
    amqpAdmin.declareExchange(new DirectExchange("amqpadmin.direct"));
    System.out.println("Create Finish");
}

效果演示

2)、创建Queue

@Test
public void createQueue(){
    amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));
    System.out.println("Create Queue Finish");
}

3)、创建Bind规则

@Test
public void createBind(){
    amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE , "amqpadmin.direct", "amqp.haha", null));
}

删除类似

@Test
public void deleteExchange(){
    amqpAdmin.deleteExchange("amqpadmin.direct");
    System.out.println("delete Finish");
}

SpringBoot的任务

1. 异步任务

用处:比如在steam上购买游戏,购买后先显示购买完成,然后会收到邮件

如果是同步任务,要等很久才会看到购买完成页面

在application类上@EnableAsync开启异步

在方法上用@Async注解,该方法就会变成异步的

一般在service中

自定义线程池:

@Configuration
@Slf4j
public class ThreadPoolConfiguration {

    @Bean(name = "defaultThreadPoolExecutor", destroyMethod = "shutdown")
    public ThreadPoolExecutor systemCheckPoolExecutorService() {

        return new ThreadPoolExecutor(3, 10, 60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(10000),
                new ThreadFactoryBuilder().setNameFormat("default-executor-%d").build(),
                (r, executor) -> log.error("system pool is full! "));
    }
}

在异步方法上添加注解@Async(“defaultThreadPoolExecutor”)

2.邮件任务

先在pom中加入starter-mail

配置

以QQ邮箱为例

#163就把qq改为163
spring.mail.host=smtp.qq.com	
spring.mail.port=587

spring.mail.username=@qq.com
#授权码,去邮箱设置里找
spring.mail.password=				

#加密认证	狂神有,blog_system没有
spring.mail.properties.mail.smtp.ssl.enable=true
自写工具类
//该例来自blog_system
@Component
public class MailUtils {
    @Autowired
    private JavaMailSenderImpl mailSender;
    @Value("${spring.mail.username}")
    private String mailfrom;

    // 发送简单邮件
    public void sendSimpleEmail(String mailto, String title, String content) {
        //  定制邮件发送内容
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(mailfrom);
        message.setTo(mailto);
        message.setSubject(title);
        message.setText(content);
        // 发送邮件
        mailSender.send(message);
    }
}

3. 定时任务

SpringTask
SpringTask是Spring自主研发的轻量级定时任务工具,相比于Quartz更加简单方便,且不需要引入其他依赖即可使用。

Cron表达式
Cron表达式是一个字符串,包括6~7个时间元素,在SpringTask中可以用于指定任务的执行时间。

@TastScheduler

@TadkExcutor

在application上@EnableScheduling

在controller的类方法上标注 @Scheduled(fixedRate = 3000) 表示这个方法会定时执行 fixedRate表示是多少毫秒

被注解@Scheduled标记的方法,是不能有参数,不然会 报错

blog_system中的定时任务:

/**
     * 定时邮件发送任务,每月1日中午12点整发送邮件
     */
@Scheduled(cron = "0 0 12 1 * ?")
//    @Scheduled(cron = "0 */3 * * * ? ")
    public void sendEmail(){
        //  定制邮件内容
        long totalvisit = statisticMapper.getTotalVisit();
        long totalComment = statisticMapper.getTotalComment();
        StringBuffer content = new StringBuffer();
        content.append("博客系统总访问量为:"+totalvisit+"人次").append("\n");
        content.append("博客系统总评论量为:"+totalComment+"人次").append("\n");
        mailUtils.sendSimpleEmail(mailto,"个人博客系统流量统计情况",content.toString());
    }

cron表示法(从左至右,除了周从小到大):

  • 分钟 0-59
  • 小时 0-23
  • 日期 1-31
  • 月份 1-12
  • 星期 0-7

*表示全部 */3表示每隔五分钟

更多内容可以上网查

其他

应用瘦身

用maven插件maven-dependency-plugin将依赖抽出。之前依赖在jar包的BOOT-INF里面

如果每次更新整个jar包,那就太大了,受不了惹~

基于springboot高校学科竞赛管理系统 springboot高级教程_spring boot


里面要是微服务,可以包含common

之后依赖进入target的lib里,自己的代码可能只有kb级别

运行方式变为

java -jar -Dloader.path="/lib" xxx-.jar