Skip to content

Commit eaf4e1c

Browse files
committed
Add README-EN.md
1 parent f880fcf commit eaf4e1c

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed

README-EN.md

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
# CacheAsMulti
2+
3+
[中文](./README.md)
4+
5+
## Installation
6+
7+
### Maven
8+
9+
```xml
10+
11+
<dependency>
12+
<groupId>io.github.ms100</groupId>
13+
<artifactId>cache-as-multi</artifactId>
14+
<version>1.1.3</version>
15+
</dependency>
16+
```
17+
18+
## Usage
19+
20+
This annotation needs to be used in conjunction with the following two sets of annotations to achieve batch caching
21+
operations on the method where the annotated parameter is located.
22+
23+
* Spring's caching annotations `@Cacheable`, `@CachePut`, `@CacheEvict`
24+
25+
* JSR-107 annotations `@CacheResult`, `@CachePut`, `@CacheRemove`, `@CacheKey`
26+
27+
> Only PROXY mode is supported, not ASPECTJ mode.
28+
29+
### @Cacheable and @CacheResult
30+
31+
#### Regular Method
32+
33+
Suppose there is a method for obtaining a single object, as follows:
34+
35+
```java
36+
class FooService {
37+
public Foo getFoo(Integer fooId) {
38+
//...
39+
}
40+
}
41+
42+
```
43+
44+
At this point, if you need to get a batch of objects, there are usually two ways to write it:
45+
46+
```java
47+
class FooService {
48+
public Map<Integer, Foo> getMultiFoo(Collection<Integer> fooIds) {
49+
//...
50+
}
51+
52+
public List<Foo> getMultiFoo(List<Integer> fooIds) {
53+
//...
54+
}
55+
}
56+
```
57+
58+
There are two changes to the method of obtaining batch objects compared to the method of obtaining single objects:
59+
60+
1. The input parameter changes from a single object (referred to as an "object parameter" below) to an object
61+
collection (referred to as an "object collection parameter" below), for example, `Integer` changes
62+
to `Collection<Integer>` or `Set<Integer>` or `List<Integer>`.
63+
2. The return value changes from a single object to `Map<K, V>` or `List<V>`. For example, `Map<Integer, Foo>`
64+
or `List<Foo>`. If the returned type is `List`, it should be the same size as the "object collection parameter" and
65+
in the same order.
66+
67+
#### Add Cache
68+
69+
In the above example, if you need to cache the method of obtaining a single object, you will use the `@Cacheable`
70+
or `@CacheResult` annotation: (PS: Here, `@CacheResult` and `@Cacheable` are used together as an example, in actual use,
71+
usually only one of them is used)
72+
73+
```java
74+
class FooService {
75+
@Cacheable(cacheNames = "foo")
76+
@CacheResult(cacheName = "foo")
77+
public Foo getFoo(Integer fooId) {
78+
// Use fooId to generate cache key and calculate condition and unless conditions, use Foo as cache value
79+
}
80+
}
81+
```
82+
83+
If `@Cacheable` or `@CacheResult` is directly added to the method of getting batch objects, a cache key will be
84+
generated for the entire【collection parameter】 and the returned `Map` or `List` will be used as a cache value.
85+
86+
However, we usually hope that it can be transformed into multiple `fooId => Foo` caches, that is: each【element】in
87+
the【collection parameter】and its corresponding value are cached separately. *At this time, just add the @CacheAsMulti
88+
annotation on the【collection parameter】to achieve the caching method we want.*
89+
90+
```java
91+
class FooService {
92+
@Cacheable(cacheNames = "foo")
93+
@CacheResult(cacheName = "foo")
94+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds) {
95+
// Generate a cache key and calculate condition and unless conditions for each element in the fooIds collection,
96+
// use the corresponding value in the Map as the cache value
97+
}
98+
99+
@Cacheable(cacheNames = "foo")
100+
@CacheResult(cacheName = "foo")
101+
public List<Foo> getMultiFoo(@CacheAsMulti List<Integer> fooIds) {
102+
// Generate a cache key and calculate condition and unless conditions for each element in the fooIds collection,
103+
// use the corresponding value in the List as the cache value
104+
// In the following examples, the handling method for returning List and returning Map is the same,
105+
// so they will not be separately demonstrated.
106+
}
107+
}
108+
```
109+
110+
When a method has multiple parameters, the key generation for caching may vary based on the configuration
111+
of `@Cacheable.key()` or the presence of `@CacheKey` annotations on the method parameters.
112+
113+
Here are some examples:
114+
115+
* When `@Cacheable.key()` is not configured or when `@CacheResult` is used and there is no `@CacheKey` on the method
116+
parameters:
117+
```java
118+
class FooService {
119+
@Cacheable(cacheNames = "foo", key="")
120+
@CacheResult(cacheName = "foo")
121+
public Foo getFoo(Integer fooId, String arg1) {
122+
// generate cache key using fooId and arg1 parameters, use return value as cache value
123+
}
124+
125+
@Cacheable(cacheNames = "foo", key="")
126+
@CacheResult(cacheName = "foo")
127+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds, String arg1) {
128+
// generate cache key for each element in fooIds parameter using arg1 parameter, use corresponding value from return Map as cache value
129+
}
130+
}
131+
```
132+
133+
* When `@Cacheable.key()` only refers to the `@CacheKey` annotated parameter of an object parameter or when `@CacheKey`
134+
is used only on the object parameter:
135+
```java
136+
class FooService {
137+
@Cacheable(cacheNames = "foo", key="#fooId")
138+
@CacheResult(cacheName = "foo")
139+
public Foo getFoo(@CacheKey Integer fooId, String arg1) {
140+
// generate cache key using fooId parameter, use return value as cache value
141+
}
142+
143+
@Cacheable(cacheNames = "foo", key="#fooIds")
144+
@CacheResult(cacheName = "foo")
145+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, String arg1) {
146+
// generate cache key for each element in fooIds parameter, use corresponding value from return Map as cache value
147+
}
148+
}
149+
```
150+
151+
* When `@Cacheable.key()` refers to multiple parameters or when there are multiple `@CacheKey` annotations on the method
152+
parameters:
153+
```java
154+
class FooService {
155+
@Cacheable(cacheNames = "foo", key="#fooId+#arg1")
156+
@CacheResult(cacheName = "foo")
157+
public Foo getFoo(@CacheKey Integer fooId, @CacheKey String arg1, Float arg2) {
158+
// generate cache key using fooId and arg1 parameters, use return value as cache value
159+
}
160+
161+
@Cacheable(cacheNames = "foo", key="#fooIds+#arg1")
162+
@CacheResult(cacheName = "foo")
163+
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti @CacheKey Collection<Integer> fooIds, @CacheKey String arg1, Float arg2) {
164+
// generate cache key for each element in fooIds parameter using arg1 parameter, use corresponding value from return Map as cache value
165+
// Note that the object collection parameter needs to have a @CacheKey annotation and needs to be included in Cacheable.key()
166+
}
167+
}
168+
```
169+
170+
### Usage with other annotations
171+
172+
* When used with Spring's `@CachePut`, it follows the same example as above.
173+
* When used with `@CacheEvict`, if the `@CacheEvict.key()` parameter in the annotation does not contain `#result`, there
174+
is no requirement for the return type of the method; if `#result` is present in the key, the return type of the method
175+
needs to be `Map` or `List`.
176+
* When used with both Spring's `@CachePut` and `@CacheEvict`, if the key parameter already contains `#result`, there is
177+
no need for a reference to the object collection parameter.
178+
* When used with `@CacheRemove`, there is no requirement for the return type of the method.
179+
* When used with JSR-107's `@CachePut`, there is no requirement for the return type of the method, and the following
180+
example can be referred to:
181+
182+
### JSR-107's @CachePut
183+
184+
* Single parameter as key, without configuring `@CacheKey`:
185+
```java
186+
class FooService {
187+
@CachePut(cacheName = "foo")
188+
public void putFoo(Integer fooId, @CacheValue String value) {
189+
// Generate the cache key using the fooId parameter and use value as the cache value
190+
}
191+
192+
@CachePut(cacheName = "foo")
193+
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap) {
194+
// In this case, the @CacheValue parameter of the method must be of type Map
195+
// Generate a cache key using each Entry key in fooIdValueMap, and use Entry value as the cache value
196+
}
197+
}
198+
```
199+
200+
* Multiple parameters as key, without configuring `@CacheKey`:
201+
```java
202+
class FooService {
203+
@CachePut(cacheName = "foo")
204+
public void putFoo(Integer fooId, String arg1, @CacheValue String value) {
205+
// Generate the cache key using the fooId and arg1 parameters, and use value as the cache value
206+
}
207+
208+
@CachePut(cacheName = "foo")
209+
public void putMultiFoo(@CacheAsMulti @CacheValue Map fooIdValueMap, String arg1) {
210+
// In this case, the @CacheValue parameter of the method must be of type Map
211+
// Generate a cache key using each Entry key in fooIdValueMap along with the arg1 parameter, and use Entry value as the cache value
212+
}
213+
}
214+
```
215+
216+
* Only the `@CacheKey` annotation is present on the object parameter:
217+
```java
218+
class FooService {
219+
@CachePut(cacheName = "foo")
220+
public void putFoo(@CacheKey Integer fooId, String arg1, @CacheValue String value) {
221+
// Generate the cache key using the fooId parameter, and use value as the cache value
222+
}
223+
224+
@CachePut(cacheName = "foo")
225+
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, String arg1) {
226+
// In this case, the @CacheValue parameter of the method must be of type Map
227+
// Generate a cache key using each Entry key in fooIdValueMap, and use Entry value as the cache value
228+
}
229+
}
230+
```
231+
232+
* If there are `@CacheKey` annotations on multiple parameters:
233+
```java
234+
class FooService {
235+
@CachePut(cacheName = "foo")
236+
public void putFoo(@CacheKey Integer fooId, @CacheKey String arg1, String arg2, @CacheValue String value) {
237+
// Generates a cache key using fooId and arg1 parameters, and uses value as the cache value
238+
}
239+
240+
@CachePut(cacheName = "foo")
241+
public void putMultiFoo(@CacheAsMulti @CacheKey @CacheValue Map fooIdValueMap, @CacheKey String arg1, String arg2) {
242+
// The @CacheValue parameter of this method must be of type Map
243+
// Generates a cache key using each key of the entries in fooIdValueMap and arg1 parameter, and uses the value of the entry as the cache value
244+
}
245+
}
246+
```
247+
248+
### Summary and Supplement
249+
250+
1. `@CacheAsMulti` annotation cannot replace the `key` parameter in Spring cache annotations, such
251+
as `@Cacheable.key()`,
252+
nor the `@CacheKey` and `@CacheValue` annotations.
253+
2. If a custom `KeyGenerator` is used, an `Object[]` will be generated by combining each `element` of
254+
the `collection parameter`
255+
and other parameters to calculate the cache key using `KeyGenerator.generate(Object, Method, Object...)`; the same
256+
goes for custom `CacheKeyGenerator`.
257+
3. When used in conjunction with cache-generating annotations, if the return type of the method is `Map`, the value of
258+
the corresponding `element` in the `Map` is `null`,
259+
then `null` will be cached, and if the `element` does not exist in the `Map`, it will not be cached.
260+
4. When used in conjunction with `@CachePut` and `@CacheEvict`, if the key parameter of the annotation is configured
261+
with `#result`,
262+
and the return type of the method is `Map`, `null` will be used as the default value to calculate the cache key and
263+
condition and unless conditions for the `element`
264+
that does not exist in the `Map`.
265+
5. `@Cacheable.condition()`, `@Cacheable.unless()` and other conditional expressions are calculated using each `element`
266+
of the `collection parameter`,
267+
and only exclude the `elements` that do not meet the conditions, rather than the entire collection.
268+
269+
## Cache Interface and Conversion
270+
271+
### EnhancedCache Interface
272+
273+
The `org.springframework.cache.Cache` interface only defines a single cache operation and does not support batch
274+
operations. Therefore, the `EnhancedCache` interface is defined to extend three batch operation
275+
methods: `multiGet`, `multiPut`, and `multiEvict`.
276+
277+
When using a certain caching medium, there needs to be a corresponding implementation of the `EnhancedCache` interface.
278+
If the medium used does not have a corresponding implementation of `EnhancedCache`, the
279+
default `EnhancedCacheConversionService.EnhancedCacheAdapter` will be used for adaptation, which implements batch
280+
operations by iterating through single operations, resulting in lower efficiency. At the same time, there will be a
281+
warn-level log when the object is created.
282+
283+
### EnhancedCacheConverter\<T\> Interface
284+
285+
Each caching medium also needs to define a converter to automatically convert `Cache` to `EnhancedCache`. The interface
286+
implemented by the converter is `EnhancedCacheConverter`. The converters registered in the `BeanFactory` will
287+
automatically be loaded into the `EnhancedCacheConversionService` to convert Spring's original `Cache`
288+
to `EnhancedCache`.
289+
290+
### Default Implementation
291+
292+
The `EnhancedCache` interface and the corresponding converters for `RedisCache`, `ConcurrentMapCache`, `Ehcache`,
293+
and `caffeineCache` have been implemented in the package, which can be viewed under `cache.convert.converter`.
294+
295+
## Working Principle
296+
297+
### Interceptor
298+
299+
1. After the standard `BeanDefinition`, modify the original `OperationSource` and `Interceptor` Bean definitions, and
300+
replace them with custom (inherited original) definitions.
301+
2. After the original `OperationSource` queries and constructs an `Operation`, query and construct a `MultiOperation`
302+
and cache it.
303+
3. Before the original `Interceptor` executes the interception, check whether the corresponding `MultiOperation` is
304+
cached. If it is, then intercept and execute.
305+
306+
### Batch Caching
307+
308+
1. Define `EnhancedCache` to extend Spring's `Cache`.
309+
2. Define `EnhancedCacheConverter` to convert `Cache` to `EnhancedCache`.
310+
3. Implement `EnhancedCache` and `EnhancedCacheConverter` in the corresponding implementation class of `Cache`.
311+
4. Define `EnhancedCacheConversionService` to automatically inject all `EnhancedCacheConverter` (including those defined
312+
by the user).
313+
5. Define `EnhancedCacheResolver` to wrap `CacheResolver`, inject `EnhancedCacheConversionService`, and convert `Cache`
314+
to `EnhancedCache` when calling `resolveCaches` to obtain `Cache`.
315+
316+
## Development Summary
317+
318+
### Utils Used
319+
320+
- `GenericTypeResolver` handles generic classes.
321+
- `ReflectionUtils` is a reflection utility class.
322+
- `ResolvableType` handles various field types, return types, and parameter types.
323+
- `AnnotationUtils` is an annotation utility class, for example, to find the annotation of the parent class.
324+
- `AnnotatedElementUtils` is a utility class for annotated elements, for example, to find merged annotations.
325+
- `MergedAnnotations` is an operation utility for merged annotations.
326+
327+
### Small Points
328+
329+
- The parameter alias of Spring's `@AliasFor` annotation is implemented using the above Spring annotation tools.
330+
- Handling of `Aware` requires explicit implementation, such as in the implementation of `BeanPostProcessor`.
331+
- If a `Map` has no corresponding `Set` implementation, you can
332+
use `Collections.newSetFromMap(new ConcurrentHashMap<>(16))`.
333+
- `AutowiredAnnotationBeanPostProcessor` handles the `@Autowired` annotation.
334+
- `ApplicationContextAwareProcessor` handles the implementation of the `ApplicationContextAware` interface.
335+
- When using reflection in Java to get parameter names, if the names are arg0, arg1, etc., in addition to the solution
336+
found online (using javac -parameters), you can also use Spring's `DefaultParameterNameDiscoverer`.
337+
338+
## Extension
339+
340+
### Cache Expiration
341+
342+
#### Does Spring Cache support cache expiration?
343+
344+
Cache expiration can be configured separately for different caches and different cache implementations, for example
345+
Redis:
346+
347+
```yaml
348+
spring:
349+
cache:
350+
redis:
351+
time-to-live: PT15M #cache for 15 minutes
352+
```
353+
354+
#### Flexible TTL
355+
356+
> Premise: Spring generates a separate Cache object for each CacheName configured in the cache annotation.
357+
358+
Usually, you can achieve this in the following three ways:
359+
360+
* Customize `CacheManager` or `CacheResolver`.
361+
* Use other caching frameworks (cannot use `@CacheAsMulti`), such as JetCache.
362+
* Customize separately for different caching implementation.
363+
364+
#### Separate configuration for Redis extension points
365+
366+
Simply implement the `RedisCacheManagerBuilderCustomizer` interface to customize configuration before
367+
the `RedisCacheManager`
368+
is generated.
369+
370+
> See `RedisCacheCustomizer` class for details.
371+
372+
After that, just add the following configuration:
373+
374+
```yaml
375+
spring:
376+
cache:
377+
redis:
378+
time-to-live: PT15M #default cache for 15 minutes
379+
cache-as-multi: #Below are the cache-as-multi configurations
380+
serialize-to-json: true #Use RedisSerializer.json() for serialization
381+
cache-name-time-to-live-map: #Cache time corresponding to cacheName
382+
foo: PT15S #foo cache for 15 seconds
383+
demo: PT5M #demo cache for 5 minutes
384+
```
385+
386+
387+

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# CacheAsMulti
22

3+
[In English](./README-EN.md)
4+
35
## 安装
46
### Maven
7+
58
```xml
69

710
<dependency>

0 commit comments

Comments
 (0)