Spring Boot 集成 Alibaba OSS
前言
本文主要介绍 Spring Boot 项目如何集成 Alibaba 的 OSS 服务(对象存储),教程内容同样适用于 Spring Cloud 项目。
提示
1、Spring Cloud 项目不建议继续使用 spring-cloud-starter-alicloud-oss
组件,尤其是较新版本的 Spring Cloud(例如 2021.0.1
版本),毕竟 Alibaba OSS 的官方文档也移除了该组件的使用说明。
2、Alibaba OSS 更多的使用教程请查看 官方文档。
版本说明
Spring Boot | Spring Boot Alibaba |
---|---|
2.6.3 | 1.0.0 |
Spring Cache 使用教程之二
大纲
前言
官方文档
使用 @Cacheable 注解
@Cacheable
可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。
属性介绍
属性名 | 描述 |
---|---|
value / cacheNames | 指定缓存的名称,Spring Cache 使用 CacheManage 管理多个缓存组件 Cache,这里的 Cache 组件就是根据该名称进行区分的,它负责对缓存执行真正的 CRUD 操作 |
key | 缓存数据时 Key 的值,默认是使用方法参数的值,可以使用 SpEL 表达式计算 Key 的值 |
keyGenerator | 缓存 Key 的生成策略,它和 key 属性互斥使用(只能二选一) |
cacheManager | 指定缓存管理器(如 ConcurrentHashMap、Redis 等) |
cacheResolver | 作用和 cacheManager 属性一样,两者只能二选一 |
condition | 指定缓存的条件(满足什么条件才缓存),可用 SpELl 表达式(如 #id>0 ,表示当入参 id 大于 0 时才缓存) |
unless | 否定缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 #result == null ,表示如果结果为 null 时不缓存) |
sync | 是否使用异步模式进行缓存,默认值是 false |
在一个多线程的环境中,某些操作可能会被相同的参数并发地调用,同一个 value
值可能被多次计算(或多次访问数据库),这样就达不到缓存的目的。针对这些可能高并发的操作,可以使用 sync
属性来告诉底层的缓存提供者将缓存的入口锁住,这样在同一时刻就只能有一个线程计算操作的结果值,而其它线程则需要等待。当 sync
的值为 true
时,相当于同步操作,可以有效地避免出现缓存击穿的问题,关于缓存击穿的介绍可以点击 这里。
特别注意
- 1、即满足
condition
又满足unless
条件的情况下,不会缓存数据 - 2、使用异步模式(
sync=true
)进行缓存时,unless
条件将不会生效 - 3、
condition
不指定相当于true
,而unless
不指定相当于false
- 4、
condition
属性使用的 SpEL 表达式只有#root
和获取方法参数类的 SpEL 表达式,不能使用带返回结果的表达式(如#result
),因此condition = "#result != null"
会导致所有对象都不写入缓存,每次都要查询数据库。
使用案例
1 |
|
指定 Key
@Cacheable
注解有一个属性 key
可以用于直接定义缓存 Key,该属性不是必填项。如果为空,则会使用默认的 Key 生成器进行生成。默认的 Key 生成器要求方法参数具有有效的 hashCode()
和 equals()
方法实现。值得一提的是,key
属性的值支持使用 SpEL 表达式。使用方法参数作为 Key 时,可以直接使用 #参数名
或者 #p参数索引
的 SpEL 表达式来引用,以下的写法都是合法的。
1 |
|
使用 @CachePut 注解
与 @Cacheable
注解不同的是使用 @CachePut
注解标注的方法,在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式写入指定的缓存中。@CachePut
注解一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。
属性介绍
@CachePut
注解所具有的属性与 @Cacheable
注解相同,这里不再累述。
使用案例
1 |
|
使用 @CacheEvict 注解
标注了 @CacheEvict
注解的方法在被调用时,会从缓存中移除已存储的数据。@CacheEvict
注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。
属性介绍
属性名 | 描述 |
---|---|
value / cacheNames | 缓存的名称 |
key | 缓存的键 |
allEntries | 是否根据缓存名称清空所有缓存数据,默认值为 false ,当值指定为 true 时,Spring Cache 将忽略注解上指定的 key 属性 |
beforeInvocation | 是否在方法执行之前就清空缓存,默认值为 false |
清除缓存的操作默认是在对应方法成功执行之后才触发的,即方法的执行如果因为抛出异常而未能成功返回时也不会触发清除操作。使用 beforeInvocation
属性可以改变触发清除操作执行的时机,当指定该属性的值为 true
时,Spring 会在调用该方法之前清除缓存中的数据。值得一提的是,在方法调用之前还是之后清除缓存的区别在于方法调用时是否会出现异常,若不出现异常,这两者之间没有区别,若出现异常,设置为在方法调用之后清除缓存将不起作用,因为方法调用失败了。
使用案例
1 |
|
使用 @Caching 注解
@Caching
注解用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解。
属性介绍
属性名 | 描述 |
---|---|
cacheable | 用于指定 @Cacheable 注解 |
put | 用于指定 @CachePut 注解 |
evict | 用于指定 @@CacheEvict 注解 |
使用案例
1 |
|
使用 @CacheConfig 注解
@CacheConfig
注解标注在类上,用于抽取 Spring Cache 相关注解的公共配置,可抽取的公共配置包括缓存名称、主键生成器、缓存管理器。比如,在每个 Spring Cache 缓存注解中,往往都指定了缓存名称(value = "stu"
或者 cacheNames = "stu"
)。此时 @CacheConfig
注解可以将它抽离出来,并在整个类上添加 @CacheConfig(value = "stu")
注解之后,每个方法默认都会使用指定的缓存名称 stu
。
属性介绍
属性名 | 描述 |
---|---|
value / cacheNames | 指定缓存的名称,Spring Cache 使用 CacheManage 管理多个缓存组件 Cache,这里的 Cache 组件就是根据该名称进行区分的,它负责对缓存执行真正的 CRUD 操作 |
cacheManager | 缓存管理器 |
keyGenerator | 主键生成器 |
使用案例
1 |
|
使用 SpEL 表达式
SpEL 表达式的语法
Spring Cache 也提供了
root
对象,可以在 SpEL 表达式中直接使用
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | 根对象 | 要调用的方法的名称 | #root.methodName |
method | 根对象 | 正在调用的方法 | #root.method.name |
target | 根对象 | 正在调用的目标对象 | #root.target |
targetClass | 根对象 | 要调用的目标的类 | #root.targetClass |
args | 根对象 | 用于调用目标的参数(作为数组) | #root.args[0] |
caches | 根对象 | 正在调用的方法使用的缓存列表(如 @Cacheable(value={"cache1", "cache2"})) ,则有两个缓存) | #root.caches[0].name |
argument name | 评估背景 | 方法参数名,可以直接使用 #参数名 ,也可以使用 #p0 或 #a0 的形式,0 代表参数的索引 | #iban、#a0、#p0 |
result | 评估背景 | 方法执行后的返回值,仅当方法执行之后的判断有效,如 unless 、cache put 、cache evict (当 beforeInvocation = false) 的表达式 | #result |
SpEL 表达式的使用案例
1 |
|
当需要使用 root
对象的属性作为 Key 时,还可以将 #root
省略掉,因为 Spring Cache 默认使用的就是 root
对象的属性
1 |
|
当需要调用目前类里的方法动态生成 Key 时,在 SpEL 表达式内拼接字符串时,必须使用单引号将字符串包裹起来
1 |
|
自定义缓存配置
防止缓存穿透
为了避免出现缓存穿透,建议让 Spring Cache 将空值也写入缓存,关于缓存穿透的介绍可以点击 这里。
1 | spring: |
指定有效时间
指定缓存数据的有效时间,这样可以让缓存数据过期被删除后,触发主动更新(基于缓存的读模式)。
1 | spring: |
提示
Spring Cache 的注解不支持给缓存单独设置不同的有效时间,若希望像 Redis 一样设置缓存的有效时间,可以参考这篇 博客。
指定 Key 前缀
在不指定 Key 前缀时,Spring Cache 默认会使用缓存的名称作为 Key 前缀。
1 | spring: |
指定 Key 生成器
缓存的本质就是键值对存储模式,每一次方法的调用都需要生成相应的 Key,这样才能操作缓存。若没有给 Spring Cache 的注解(如 @Cacheable
)设置属性 key
,缓存抽象默认会使用 SimpleKeyGenerator
来自动生成 Key,具体源码如下:
- 如果没有方法参数,则直接返回
SimpleKey.EMPTY
- 如果只有一个方法参数,则直接返回该方法参数
- 若有多个方法参数,则返回包含多个方法参数的 SimpleKey 对象
Spring Cache 也考虑到需要自定义 Key 的生成方式,只需要实现 org.springframework.cache.interceptor.KeyGenerator
接口,然后通过 @Cacheable
注解的 keyGenerator
属性指定 Key 生成器即可。值得一提的是,默认的 Key 生成器要求方法参数具有有效的 hashCode()
和 equals()
方法实现。
1 | /** |
1 | /** |
指定序列化机制
默认情况下,Spring Cache 会使用 JDK 的序列化机制将缓存数据写入 Redis,这样存储在 Redis 里面的就是二进制数据。若希望 Spring Cache 将数据序列化成 JSON 数据再写入 Redis,可以使用以下的配置类。
1 | import org.springframework.boot.autoconfigure.cache.CacheProperties; |
Spring Cache 加载 Redis 缓存配置的流程
- CacheAutoConfiguration --> RedisCacheConfiguration --> 自动配置了 RedisCacheManager --> 初始化所有缓存 --> 每个缓存决定使用什么配置内容 --> 如果 RedisCacheConfiguration 有就用已经有的,没有就用默认的 Redis 配置
- 所以如果想自定义 Redis 缓存配置,只需要在 Spring 容器中放一个 RedisCacheConfiguration,它就会应用到当前 RedisCacheManager 管理的所有缓存分区中
常见问题总结
缓存不生效
在有些情形下,Spring Cache 注解式缓存是不起作用的。比如在同一个 Bean 里的内部方法调用,又或者是子类调用父类中有缓存注解的方法等。后者不起作用是因为缓存切面必须走代理才有效,这时候可以手动使用 CacheManager
来获得缓存效果。
Spring Cache 的不足
读模式
- 读模式下,可能会出现缓存失效的问题,Spring Cache 的解决方案如下
缓存穿透
:查询一个不存在的数据(Null),解决方案是缓存空数据,配置内容是cache-null-values: true
缓存雪崩
:大量缓存同时过期,解决方案是给缓存设置过期时间(或者是随机的过期时间),配置内容是time-to-live: 3600000
缓存击穿
:大量并发请求进来同时查询一个正好过期的数据,解决方案是使用@Cacheable(sync = true)
来实现同步模式的缓存写入,底层是基于 JDK 的synchronized
- 读模式下,可能会出现缓存失效的问题,Spring Cache 的解决方案如下
写模式
- 双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache 暂时没办法解决,其他的解决方案如下
- 加分布式锁(读写锁),只适用于读多写少的业务场景
- 直接查询数据库,不再从缓存获取数据,只适用于读多写多的业务场景
- 使用 Canal 中间件,实时将数据库的数据更新到缓存,会增加系统的复杂性
- 双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache 暂时没办法解决,其他的解决方案如下
总结
- 常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了
- 特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)
提示
更多关于缓存写模式、缓存失效、缓存数据一致性、分布式锁的介绍,请点击 这里。
参考博客
Spring Cache 使用教程之一
大纲
前言
官方文档
简单介绍
Spring Cache 是 Spring 提供的一个缓存框架,从 Spring 3.1 版本开始支持将缓存添加到现有的 Spring 应用程序中,从 Spring 在 4.1 版本开始,缓存已支持 JSR-107
注释和更多自定义的选项。Spring Cache 利用了 AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解就能实现缓存功能,做到了较小的代码侵入性。由于市面上的缓存工具实在太多,Spring Cache 框架还提供了 CacheManager 接口,可以实现降低对各种缓存框架的耦合;它不是具体的缓存实现,只是提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如 Ehcache、Caffeine、Hazelcast、Couchbase 等。
Spring 与 SpringBoot 配置跨域的几种方式
前言
跨域介绍
什么是跨域
:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一项不同,都属于跨域造成的原因
:由于浏览器的同源策略,即 A 网站只能访问 A 网站的内容,不能访问 B 网站的内容特别注意
:跨域问题只存在于浏览器,也就是说当前端页面访问后端的接口时,返回值是有的,只是服务器没有在请求头指定跨域的信息,所以浏览器自动把返回值给” 屏蔽了”解决跨域
:经过上面的了解,可以得出几个解决跨域的方法(这里暂不考虑前端的实现方案),一是服务端指定跨域信息,二是在 Web 页面与后端服务之间加一层服务来指定跨域信息,比如代理服务 Nginx
提示
更多关于跨域的详细介绍内容,可以看 这里。
博客导流微信公众号
前言
博客将流量导向微信公众号很简单,可以使用 TechGrow 的免费导流工具实现,用户扫码关注微信公众号后可以解锁全站文章,让微信公众号的粉丝数躺着增长。整个过程只需六步就可以搞定,适用于各类主流的博客,本文以 Hexo 的 NexT 主题博客举例。
提示
- TechGrow 开放平台的 官方文档
- 若使用的是 Hexo 静态博客,建议直接安装 hexo-readmore 插件,详细教程可点击这里
- 若使用的是 VuePress v1.x 静态博客,建议直接安装 vuepress-plugin-readmore-popular 插件,详细教程可点击这里
特色功能
- 兼容主流的博客框架
- 支持随机为博客添加引流功能
- 支持查询用户解锁文章的历史记录
- 支持自定义或者动态计算文章内容的预览高度
- 支持自定义 CSS 样式,轻松适配不同风格的博客
第一步:注册博客
浏览器访问 TechGrow 的官网 ,注册并登录账号后,进入博客的后台管理页面。首先点击左侧的菜单 博客注册
,然后点击 新增
按钮,添加自己博客的信息。博客注册成功后,记录下 博客 ID
,后面的步骤会使用到
Vue 页面读取并展示 Markdown 文件
前言
如何在 Vue 中读取项目本地的 MarkDown 文件并展示在网页上呢?查阅资料后发现,一般的方案是在 Vue 页面中引入 Markdown 编辑器,然后利用编辑器的预览功能来展示 MarkDown 文件的内容。推荐使用开源的 MarkDown 编辑器 mavonEditor 或者 vue-meditor。
vue-meditor 介绍
简介
Docker 构建 Frp 镜像
构建 Frps 镜像
- Dockerfile 编写
1 | FROM amd64/alpine:3.10 |
CMake 入门教程之三单元测试
前言
CMake 是一个跨平台的 C/C++ 项目组织管理工具,虽然许多 IDE 都有私有的项目管理工具,但是在现在各大 IDE 基本都支持使用 CMake 管理项目,所以如果有跨平台的需求,使用 CMake 管理是最方便的。值得一提的是,CMake 支持 gtest、cppunit
等单元测试框架,当然也可以使用断言自定义单元测试。
创建简单的带单元测试的项目
创建项目工程
下载代码
点击下载 完整的案例代码,项目的目录结构如下:
1 | minder-test |