<dependency>
<groupId>io.github.ms100</groupId>
<artifactId>cache-as-multi</artifactId>
<version>1.3.1</version>
</dependency>若批量方法返回的 List 不能保证与【对象集合参数】大小相同并顺序一致时,可以使用 @CacheAsMulti.asElementField
指定【对象集合参数】的元素在 List 的元素中所在的字段。这将更适合数据库查询。
class CarService {
@Cacheable(cacheNames = "car")
@CacheResult(cacheName = "car")
public List<CarPO> findCars(@CacheAsMulti(asElementField = "info.id") List<Integer> ids) {
// 返回值 List 的大小不必与 ids 相同
}
public static class CarPO {
private CarInfoPO info;
private String name;
}
public static class CarInfoPO {
private Integer id;
}
}本注解需要与下面两套注解搭配使用,以实现对被注解参数所在的方法进行批量的缓存操作。
-
Spring的缓存注解
@Cacheable、@CachePut、@CacheEvict -
JSR-107的注解
@CacheResult、JSR-107的@CachePut、@CacheRemove、@CacheKey
只支持 PROXY 模式,不支持 ASPECTJ 模式
假设已有获取单个对象的方法,如下:
class FooService {
public Foo getFoo(Integer fooId) {
//...
}
}此时如果需要获取批量对象的方法,通常会是下面两种写法:
class FooService {
public Map<Integer, Foo> getMultiFoo(Collection<Integer> fooIds) {
//...
}
public List<Foo> getMultiFoo(List<Integer> fooIds) {
//...
}
}获取批量对象的方法相对于获取单个对象的方法会有两点变化:
- 入参从单个对象(以下称【对象参数】)变为对象集合(以下称【对象集合参数】),例如
Integer变为Collection<Integer>或Set<Integer>或List<Integer>。 - 返回值从单个对象变为
Map<K,V>或者List<V>。例如Map<Integer,Foo>或List<Foo>,若返回的是List类型,那应与【对象集合参数】大小相同并顺序一致(PS: v1.3版本之后不再有此限制,详见更新细节)。
在上面例子中,如果需要对获取单个对象的方法做缓存,会使用 @Cacheable 或 @CacheResult 注解: (PS: 这里将 @CacheResult
和 @Cacheable 放在一起举例子,实际使用时通常只用其中的一个)
class FooService {
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public Foo getFoo(Integer fooId) {
// 用 fooId 生成缓存 key 和计算 condition、unless 条件,用 Foo 为缓存值
}
}如果对获取批量对象的方法直接加上 @Cacheable 或 @CacheResult,则会使用【对象集合参数】整体生成一个缓存 key,将返回的 Map 或 List 整体作为一个缓存值。
但通常我们会希望它能变为多个 fooId => Foo 的缓存,即:使用【对象集合参数】中每个【元素】和它对应的值分别作缓存。此时只需要在【对象集合参数】上加上 @CacheAsMulti 注解即可实现我们想要的缓存方式。
class FooService {
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public Map<Integer, Foo> getMultiFoo(@CacheAsMulti Collection<Integer> fooIds) {
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 Map 中对应的值作为缓存值
}
@Cacheable(cacheNames = "foo")
@CacheResult(cacheName = "foo")
public List<Foo> getMultiFoo(@CacheAsMulti List<Integer> fooIds) {
// 为 fooIds 集合中每个元素分别生成缓存 key 和计算 condition、unless 条件,用 List 中对应的值作为缓存值
// 之后的例子中,返回 List 和 返回 Map 的处理方式都一样,就不再单独举例
}
}- 与 Spring 的
@CachePut搭配时,同样符合上面的例子。 - 与
@CacheEvict搭配时,若注解的@CacheEvict.key()参数中没有#result,对【方法】返回类型无要求;若 key 中有#result,【方法】返回类型需要是Map或List。 - 与 Spring 的
@CachePut、@CacheEvict搭配,若 key 参数中已有#result, 则可以没有【对象集合参数】的引用。 - 与
@CacheRemove搭配时,对【方法】返回类型无要求。 - 与 JSR-107 的
@CachePut搭配时,对【方法】返回类型无要求,可参照下面的示例:
单个参数做key,未配置 @CacheKey:
class FooService {
@CachePut(cacheName = "foo")
public void putFoo(Integer fooId, @CacheValue String value) {
// 用 fooId 参数生成缓存的 key,用 value 作为缓存值
}
@CachePut(cacheName = "foo")
public void putMultiFoo(@CacheAsMulti @CacheValue Map<Integer, String> fooIdValueMap) {
// 此时方法的 @CacheValue 参数必须为 Map 类型
// 用 fooIdValueMap 中的每个 Entry 的 key 分别生成缓存的 key,用 Entry 的 value 作为缓存值
}
}@CacheAsMulti注解不能替代 Spring 缓存注解中的 key 参数,例如:@Cacheable.key(),也不能替代@CacheKey、@CacheValue注解。- 如果使用自定义的
KeyGenerator,则会用【对象集合参数】的每个【元素】和其他参数组成Object[]传入KeyGenerator.generate(Object, Method, Object...)计算缓存 key;自定义的CacheKeyGenerator也一样。 - 与
@Cacheable、@CacheResult、@CachePut注解搭配使用时,若CacheAsMulti.strictNull()为true且方法的返回类型是Map,【元素】在Map中对应的值为null就会缓存null,【元素】在Map中不存在就不缓存。 - 与
@CachePut和@CacheEvict搭配,注解的 key 参数配置了#result时,若方法的返回类型是Map,对于Map中不存在的【元素】会使用null作为缺省值来计算缓存 key 和 condition、unless 条件。 @Cacheable.condition()、@Cacheable.unless()等条件表达式是用【对象集合参数】中的每个【元素】分别计算,只将不符合的【元素】排除,而不是整个集合。
org.springframework.cache.Cache 接口只定义了单个缓存操作,并不支持批量操作,为此定义了 EnhancedCache 接口扩充了 multiGet、multiPut、multiEvict 三个批量操作方法。
当使用某种缓存介质时,需要有对应的 EnhancedCache 接口实现。如果使用的介质没有对应的 EnhancedCache 实现,则会使用默认的 EnhancedCacheConversionService.EnhancedCacheAdapter 进行适配,会使用循环单个操作来实现批量操作,效率较低。同时在对象创建的时候会出现一条 warn 级别的日志。
每种缓存介质还需要定义一个转换器用来将 Cache 自动转为 EnhancedCache,转换器实现的接口为 EnhancedCacheConverter。BeanFactory 中注册的转换器会自动加载到 EnhancedCacheConversionService 中用来将 Spring 原有的 Cache 转为 EnhancedCache。
包里已经实现了 RedisCache、ConcurrentMapCache、Ehcache、caffeineCache 的 EnhancedCache 接口和相应的转换器,具体可到 cache.convert.converter 下查看。
- 在标准的 BeanDefinition 之后,修改原有的
OperationSource和Interceptor的 Bean 定义,使用自定义的(继承原有的)来替换。 - 在原有
OperationSource查询构建Operation后,查询构建MultiOperation并缓存。 - 在原有
Interceptor执行拦截前,查询是否缓存有对应的MultiOperation,如果有则拦截执行。
- 定义
EnhancedCache扩充 Spring 的Cache。 - 定义
EnhancedCacheConverter将Cache转为EnhancedCache。 - 对应
Cache的实现类,增加EnhancedCache和EnhancedCacheConverter的实现类。 - 定义
EnhancedCacheConversionService将所有EnhancedCacheConverter(包括使用者自定义的)自动注入进来。 - 定义
EnhancedCacheResolver包装CacheResolver,并注入EnhancedCacheConversionService,在调用resolveCaches获取Cache时将其转换为EnhancedCache。
GenericTypeResolver处理泛型类ReflectionUtils反射工具类ResolvableType处理各种字段类型、返回类型、参数类型AnnotationUtils注解工具类,例如向上找父类的注解AnnotatedElementUtils被注释的元素工具类,例如查找合并注解MergedAnnotations合并注解的操作工具
- Spring 的
@AliasFor注解参数别名就是利用上述的 Spring 注解工具实现的。 Aware的处理需要显示的实现,例如在BeanPostProcessor的实现中。- 如果一个
Map没有对应的Set实现,可以用Collections.newSetFromMap(new ConcurrentHashMap<>(16))。 - 处理
@Autowried注解的是AutowiredAnnotationBeanPostProcessor。 - 处理实现
ApplicationContextAware接口的是ApplicationContextAwareProcessor。 - java使用反射获取参数名得到的是arg0、arg1时,除了网上的解决办法(javac -parameters),还可以使用spring的 DefaultParameterNameDiscoverer。
针对不同的缓存缓存方式单独配置。例如Redis:
spring:
cache:
redis:
time-to-live: PT15M #缓存15分钟前提:Spring 为缓存注解中每一个配置的 CacheName,都生成一个单独的 Cache 对象。
通常可以通过下面三种方式来实现:
- 自定义
CacheManager或CacheResolver。 - 使用其他缓存框架(不能再使用
@CacheAsMulti),例如 JetCache。 - 针对不同的缓存的扩展点单独定制。
只要实现 RedisCacheManagerBuilderCustomizer 接口,就可以在 RedisCacheManager 生成前,设置 RedisCacheManagerBuilder 实现自定义配置。
具体实现查看
RedisCacheCustomizer类。
之后只需增加如下配置:
spring:
cache:
redis:
time-to-live: PT15M #默认缓存15分钟
cache-as-multi: #下面是 cache-as-multi 的配置
serialize-to-json: true #使用 RedisSerializer.json() 序列化
cache-name-time-to-live-map: #cacheName对应的缓存时间
foo: PT15S #foo缓存15秒
demo: PT5M #demo缓存5分钟