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.direct、exchange.fanout、exchange.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包,那就太大了,受不了惹~
里面要是微服务,可以包含common
之后依赖进入target的lib里,自己的代码可能只有kb级别
运行方式变为
java -jar -Dloader.path="/lib" xxx-.jar