Skip to content

Commit 7c799c5

Browse files
committed
first commit
1 parent 5325352 commit 7c799c5

File tree

90 files changed

+1725
-377
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+1725
-377
lines changed

README.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
# CacheAsMulti
2+
3+
## 安装
4+
### Maven
5+
```xml
6+
<dependency>
7+
<groupId>io.github.ms100</groupId>
8+
<artifactId>cache-as-multi</artifactId>
9+
<version>1.0.0</version>
10+
</dependency>
11+
```
12+
13+
14+
## 使用:
15+
16+
本注解需要与下面两套注解搭配使用,以实现对被注解参数所在的方法进行批量的缓存操作。
17+
18+
* Spring的缓存注解 `@Cacheable``@CachePut``@CacheEvict`
19+
20+
* JSR-107的注解 `@CacheResult`、JSR-107的`@CachePut``@CacheRemove``@CacheKey`
21+
22+
> 只支持 PROXY 模式,不支持 ASPECTJ 模式
23+
24+
### @Cacheable@CacheResult
25+
26+
#### 普通方法
27+
假设已有获取单个对象的方法,如下:
28+
```java
29+
class FooService {
30+
public Foo getFoo(Integer fooId) {
31+
//...
32+
}
33+
}
34+
35+
```
36+
37+
此时如果需要获取批量对象的方法,通常会是下面两种写法:
38+
```java
39+
class FooService {
40+
public Map<Integer, Foo> getMultiFoo(Collection<Integer> fooIds) {
41+
//...
42+
}
43+
44+
public List<Foo> getMultiFoo(List<Integer> fooIds) {
45+
//...
46+
}
47+
}
48+
```
49+
50+
获取批量对象的方法相对于获取单个对象的方法会有两点变化:
51+
1. 入参从单个对象(以下称【对象参数】)变为对象集合(以下称【对象集合参数】),例如 `Integer` 变为 `Collection<Integer>``Set<Integer>``List<Integer>`
52+
2. 返回值从单个对象变为 `Map<K,V>` 或者 `List<V>` 。例如 `Map<Integer,Foo>``List<Foo>`,若返回的是 `List` 类型,那应与【对象集合参数】大小相同并顺序一致。
53+
54+
#### 加缓存
55+
在上面例子中,如果需要对获取单个对象的方法做缓存,会使用 `@Cacheable``@CacheResult` 注解: (PS: 这里将 `@CacheResult``@Cacheable` 放在一起举例子,实际使用时通常只用其中的一个)
56+
```java
57+
class FooService {
58+
@Cacheable(cacheNames = "foo")
59+
@CacheResult(cacheName = "foo")
60+
public Foo getFoo(Integer fooId) {
61+
// 用 fooId 生成缓存 key 和计算 condition、unless 条件,用 Foo 为缓存值
62+
}
63+
}
64+
```
65+
66+
如果对获取批量对象的方法直接加上 `@Cacheable``@CacheResult`,则会使用【对象集合参数】整体生成一个缓存 key,将返回的 `Map``List` 整体作为一个缓存值。
67+
68+
但通常我们会希望它能变为多个 `fooId => Foo` 的缓存,即:使用【对象集合参数】中每个【元素】和它对应的值分别作缓存。**此时只需要在【对象集合参数】上加上 @CacheAsMulti 注解即可实现我们想要的缓存方式。**
69+
```java
70+
class FooService {
71+
@Cacheable(cacheNames = "foo")
72+
@CacheResult(cacheName = "foo")
73+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds) {
74+
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 Map 中对应的值作为缓存值
75+
}
76+
77+
@Cacheable(cacheNames = "foo")
78+
@CacheResult(cacheName = "foo")
79+
public List<Foo> getMultiFoo(@CacheAsMulti List<Integer> fooIds) {
80+
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 List 中对应的值作为缓存值
81+
// 之后的例子中,返回 List 和 返回 Map 的处理方式都一样,就不再单独举例
82+
}
83+
}
84+
```
85+
86+
#### 当方法有多个参数时
87+
示例如下:
88+
89+
* 使用 `@Cacheable``@Cacheable.key()` 未配置【或】使用 `@CacheResult` 时参数中没有 `@CacheKey`
90+
```java
91+
class FooService {
92+
@Cacheable(cacheNames = "foo", key="")
93+
@CacheResult(cacheName = "foo")
94+
public Foo getFoo(Integer fooId, String arg1) {
95+
// 用 fooId 和 arg1 两个参数生成缓存的 key,用返回值作为缓存值
96+
}
97+
98+
@Cacheable(cacheNames = "foo", key="")
99+
@CacheResult(cacheName = "foo")
100+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds, String arg1) {
101+
// 用 fooIds 中的每个【元素】分别和 arg1 参数生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
102+
}
103+
}
104+
```
105+
106+
* 使用 `@Cacheable``@Cacheable.key()` 只配置了【对象参数】的引用【或】使用 `@CacheResult` 时只有【对象参数】上有 `@CacheKey`
107+
```java
108+
class FooService {
109+
@Cacheable(cacheNames = "foo", key="#fooId")
110+
@CacheResult(cacheName = "foo")
111+
public Foo getFoo(@CacheKey Integer fooId, String arg1) {
112+
// 用 fooId 生成缓存的 key,用返回值作为缓存值
113+
}
114+
115+
@Cacheable(cacheNames = "foo", key="#fooIds")
116+
@CacheResult(cacheName = "foo")
117+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, String arg1) {
118+
// 用 fooIds 中的每个【元素】分别生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
119+
}
120+
}
121+
```
122+
123+
* 使用 `@Cacheable``@Cacheable.key()` 配置了若干参数的引用【或】使用 `@CacheResult` 时参数中有若干 `@CacheKey`
124+
```java
125+
class FooService {
126+
@Cacheable(cacheNames = "foo", key="#fooId+#arg1")
127+
@CacheResult(cacheName = "foo")
128+
public Foo getFoo(@CacheKey Integer fooId, @CacheKey String arg1, Float arg2) {
129+
// 用 fooId 和 arg1 两个参数生成缓存的 key,用返回值作为缓存值
130+
}
131+
132+
@Cacheable(cacheNames = "foo", key="#fooIds+#arg1")
133+
@CacheResult(cacheName = "foo")
134+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, @CacheKey String arg1, Float arg2) {
135+
// 用 fooIds 中的每个【元素】分别和 arg1 参数生成缓存的 key,用返回 Map 中【元素】对应的值作为缓存值
136+
// 注意此时【对象集合参数】需要在 Cacheable.key() 中,需要有 @CacheKey 注解
137+
}
138+
}
139+
```
140+
141+
### 与其他注解搭配使用时的说明:
142+
* 与 Spring 的 `@CachePut` 搭配时,同样符合上面的例子。
143+
*`@CacheEvict` 搭配时,若注解的 `@CacheEvict.key()` 参数中没有 `#result`,对【方法】返回类型无要求;若 key 中有 `#result`,【方法】返回类型需要是 `Map``List`
144+
* 与 Spring 的 `@CachePut``@CacheEvict` 搭配,若 key 参数中已有 `#result`, 则可以没有【对象集合参数】的引用。
145+
*`@CacheRemove` 搭配时,对【方法】返回类型无要求。
146+
* 与 JSR-107 的 `@CachePut` 搭配时,对【方法】返回类型无要求,可参照下面的示例:
147+
148+
### JSR-107 的 @CachePut
149+
* 单个参数做key,未配置 `@CacheKey`
150+
```java
151+
class FooService {
152+
@CachePut(cacheName = "foo")
153+
public void putFoo(Integer fooId, @CacheValue String value) {
154+
// 用 fooId 参数生成缓存的 key,用 value 作为缓存值
155+
}
156+
157+
@CachePut(cacheName = "foo")
158+
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap) {
159+
// 此时方法的 @CacheValue 参数必须为 Map 类型
160+
// 用 fooIdValueMap 中的每个 Entry 的 key 分别生成缓存的 key,用 Entry 的 value 作为缓存值
161+
}
162+
}
163+
```
164+
165+
* 多个参数做key,未配置 `@CacheKey`
166+
```java
167+
class FooService {
168+
@CachePut(cacheName = "foo")
169+
public void putFoo(Integer fooId, String arg1, @CacheValue String value) {
170+
// 用 fooId 和 arg1 两个参数生成缓存的 key,用 value 作为缓存值
171+
}
172+
173+
@CachePut(cacheName = "foo")
174+
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap, String arg1) {
175+
// 此时方法的 @CacheValue 参数必须为 Map 类型
176+
// 用 fooIdValueMap 中的每个 Entry 的 key 分别和 arg1 参数生成缓存的 key,用 Entry 的 value 作为缓存值
177+
}
178+
}
179+
```
180+
181+
* 只有【对象参数】上有 `@CacheKey`
182+
```java
183+
class FooService {
184+
@CachePut(cacheName = "foo")
185+
public void putFoo(@CacheKey Integer fooId, String arg1, @CacheValue String value) {
186+
// 用 fooId 参数生成缓存的 key,用 value 作为缓存值
187+
}
188+
189+
@CachePut(cacheName = "foo")
190+
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, String arg1) {
191+
// 此时方法的 @CacheValue 参数必须为 Map 类型
192+
// 用 fooIdValueMap 中的每个 Entry 的 key 分别生成缓存的 key,用 Entry 的 value 作为缓存值
193+
}
194+
}
195+
```
196+
197+
* 若干参数上有 `@CacheKey`
198+
```java
199+
class FooService {
200+
@CachePut(cacheName = "foo")
201+
public void putFoo(@CacheKey Integer fooId, @CacheKey String arg1, String arg2, @CacheValue String value) {
202+
// 用 fooId 和 arg1 两个参数生成缓存的 key,用 value 作为缓存值
203+
}
204+
205+
@CachePut(cacheName = "foo")
206+
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, @CacheKey String arg1, String arg2) {
207+
// 此时方法的 @CacheValue 参数必须为 Map 类型
208+
// 用 fooIdValueMap 中的每个 Entry 的 key 分别和 arg1 参数生成缓存的 key,用 Entry 的 value 作为缓存值
209+
}
210+
}
211+
```
212+
213+
### 总结和补充:
214+
1. `@CacheAsMulti` 注解不能替代 Spring 缓存注解中的 key 参数,例如:`@Cacheable.key()`,也不能替代 `@CacheKey``@CacheValue` 注解。
215+
2. 如果使用自定义的 `KeyGenerator`,则会用【对象集合参数】的每个【元素】和其他参数组成 `Object[]` 传入 `KeyGenerator.generate(Object, Method, Object...)` 计算缓存 key;自定义的 `CacheKeyGenerator` 也一样。
216+
3. 与生成缓存的注解搭配使用时,若方法的返回类型是 `Map`,【元素】在 `Map` 中对应的值为 `null` 就会缓存 `null`,【元素】在 `Map` 中不存在就不缓存。
217+
4.`@CachePut``@CacheEvict` 搭配,注解的 key 参数配置了 `#result` 时,若方法的返回类型是 `Map`,对于 `Map` 中不存在的【元素】会使用 `null` 作为缺省值来计算缓存 key 和 condition、unless 条件。
218+
5. `@Cacheable.condition()``@Cacheable.unless()` 等条件表达式是用【对象集合参数】中的每个【元素】分别计算,只将不符合的【元素】排除,而不是整个集合。
219+
220+
## 缓存接口及转换
221+
222+
### EnhancedCache 接口
223+
`org.springframework.cache.Cache` 接口只定义了单个缓存操作,并不支持批量操作,为此定义了 `EnhancedCache` 接口扩充了 `multiGet``multiPut``multiEvict` 三个批量操作方法。
224+
225+
当使用某种缓存介质时,需要有对应的 `EnhancedCache` 接口实现。如果使用的介质没有对应的 `EnhancedCache` 实现,则会使用默认的 `EnhancedCacheConversionService.EnhancedCacheAdapter` 进行适配,会使用循环单个操作来实现批量操作,效率较低。同时在对象创建的时候会出现一条 warn 级别的日志。
226+
227+
### EnhancedCacheConverter\<T\> 接口
228+
每种缓存介质还需要定义一个转换器用来将 `Cache` 自动转为 `EnhancedCache`,转换器实现的接口为 `EnhancedCacheConverter`。BeanFactory 中注册的转换器会自动加载到 `EnhancedCacheConversionService` 中用来将 Spring 原有的 `Cache` 转为 `EnhancedCache`
229+
230+
### 默认实现
231+
包里已经实现了 `RedisCache``ConcurrentMapCache``Ehcache``caffeineCache``EnhancedCache` 接口和相应的转换器,具体可到 `cache.convert.converter` 下查看。
232+
233+
234+
## 工作原理
235+
### 拦截器
236+
1. 在标准的 BeanDefinition 之后,修改原有的 `OperationSource``Interceptor` 的 Bean 定义,使用自定义的(继承原有的)来替换。
237+
2. 在原有 `OperationSource` 查询构建 `Operation` 后,查询构建 `MultiOperation` 并缓存。
238+
3. 在原有 `Interceptor` 执行拦截前,查询是否缓存有对应的 `MultiOperation`,如果有则拦截执行。
239+
240+
### 批量缓存
241+
1. 定义 `EnhancedCache` 扩充 Spring 的 `Cache`
242+
2. 定义 `EnhancedCacheConverter``Cache` 转为 `EnhancedCache`
243+
3. 对应 `Cache` 的实现类,增加 `EnhancedCache``EnhancedCacheConverter` 的实现类。
244+
4. 定义 `EnhancedCacheConversionService` 将所有 `EnhancedCacheConverter` (包括使用者自定义的)自动注入进来。
245+
5. 定义 `EnhancedCacheResolver` 包装 `CacheResolver`,并注入 `EnhancedCacheConversionService`,在调用 `resolveCaches` 获取 `Cache` 时将其转换为 `EnhancedCache`
246+
247+
## 开发总结
248+
249+
### 用到的 Utils
250+
- `GenericTypeResolver` 处理泛型类
251+
- `ReflectionUtils` 反射工具类
252+
- `ResolvableType` 处理各种字段类型、返回类型、参数类型
253+
- `AnnotationUtils` 注解工具类,例如向上找父类的注解
254+
- `AnnotatedElementUtils` 被注释的元素工具类,例如查找合并注解
255+
- `MergedAnnotations` 合并注解的操作工具
256+
257+
### 小知识点
258+
- Spring 的 `@AliasFor` 注解参数别名就是利用上述的 Spring 注解工具实现的。
259+
- `Aware` 的处理需要显示的实现,例如在 `BeanPostProcessor` 的实现中。
260+
- 如果一个 `Map` 没有对应的 `Set` 实现,可以用 `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`
261+
- 处理 `@Autowried` 注解的是 `AutowiredAnnotationBeanPostProcessor`
262+
- 处理实现 `ApplicationContextAware` 接口的是 `ApplicationContextAwareProcessor`
263+
- java使用反射获取参数名得到的是arg0、arg1时,除了网上的解决办法(javac -parameters),还可以使用spring的 DefaultParameterNameDiscoverer。
264+
265+
## 扩展
266+
267+
### 缓存有效期
268+
269+
#### Spring 的缓存没有有效期?
270+
271+
针对不同的缓存缓存方式单独配置。例如Redis:
272+
273+
```yaml
274+
spring:
275+
cache:
276+
redis:
277+
time-to-live: PT15M #缓存15分钟
278+
```
279+
280+
#### 灵活的TTL
281+
282+
> 前提:Spring 为缓存注解中每一个配置的 CacheName,都生成一个单独的 Cache 对象。
283+
284+
通常可以通过下面三种方式来实现:
285+
* 自定义 `CacheManager` 或 `CacheResolver`。
286+
* 使用其他缓存框架(不能再使用 `@CacheAsMulti`),例如 JetCache。
287+
* 针对不同的缓存的扩展点单独定制。
288+
289+
#### 针对Redis的扩展点单独配置
290+
291+
有 `RedisCacheManagerBuilderCustomizer` 接口,可以在 `RedisCacheManager` 生成前,自定义 `RedisCacheManagerBuilder` 想要的配置。
292+
293+
> 具体实现查看 `RedisCacheSpecificTimeToLiveCustomizer`。
294+
295+
之后只需增加配置 cache-name-time-to-live:
296+
```yaml
297+
spring:
298+
cache:
299+
redis:
300+
time-to-live: PT15M #默认缓存15分钟
301+
cache-name-time-to-live: #缓存key的缓存时间
302+
maps:
303+
foo: PT15S #foo缓存15秒
304+
demo: PT5M #demo缓存5分钟
305+
```
306+
307+
308+
309+
310+
311+
312+

0 commit comments

Comments
 (0)