一、Spring Cache Abstraction
spring提供了对缓存的支持,提供了Spring Cache Abstraction,使得我们不用关注缓存细节。Spring Cache Abstraction并不提供具体的实现,就像JDBC一样,各种缓存中间件可以实现Spring Cache Abstraction用来支持缓存。比如redis,当我们引入redis时,会自动配置RedisCacheManager,提供针对Spring Cache Abstraction的具体实现。
在使用缓存前我们需要通过@EnableCaching开启缓存
@Configuration
@EnableCaching
public class AppConfig {
}
二、声明缓存操作的注解
2.1 @Cacheable(应用于查询方法上)
顾名思义,当方法被此注解标注后,表明方法结果将被缓存,下次再以相同入参调用此方法时,将从缓存取出数据返回而不发生真实的方法调用,最简单的使用就是只需要指定一下name前缀,第一次执行此方法时,若缓存中没有,则执行真实调用之后,根据指定的name与以及入参使用key生成器生成对应的key,缓存在缓存服务器中。
2.1.1基本使用
@Cacheable("user-info")
public Optional<UserInfo> getUserInfo(int userId) {
return userInfoRepository.findById(userId);
}
当然可能有些情况下,我们希望某个方法可以从多个缓存中去命中,比如只要key1,key2,key3中有个值对应的缓存存在,那么就将此缓存值返回,达到上述目的,只需要指定cacheNames,类似这样声明@Cacheable({"user-info1,user-info2"}),当第一次执行方法时,将会为上述数组中指定的所有key前缀都生成缓存。
2.1.2 key的生成策略
Spring Cache Abstraction默认使用SimpleKeyGenerator作为key的生成器,拥有如下生成策略
1)如果方法没有任何入参,那么将默认为SimpleKey.EMPTY
2)如果方法只有一个入参,那么则返回该入参实例
3)如果方法有多个入参,则将这些入参包裹未SimpleKey返回
可能上述策略还不能满足我们日常开发的需要,比如我的方法虽然入参有多个,但是其实我只需要多个入参其中某个参数或者入参对象的某个属性去标识缓存的key就可以了,这种场景就需要通过指定@Cacheable注解中的key,key可传入SPEL表达式,通过SPEL表达式甚至可以直接调用某个类的静态方法,类似这样:
// 通过给指定类传入指定入参,调用静态方法生成缓存key
@Cacheable(cacheNames = "user", key = "T(com.cf.study.utils.CacheKeyUtils).getKey(#userId)")
public Optional<UserInfo> getUserInfo(int userId) {
...
}
// 通过选择多个入参中其中一个入参生成缓存key
@Cacheable(cacheNames = "user", key = "#userName")
public Optional<UserInfo> getUserInfo(int userId,String userName) {
...
}
// 通过选择入参对象的其中某个属性值生成缓存key
@Cacheable(cacheNames = "user", key = "#userInfo.userId")
public Optional<UserInfo> getUserInfo(UserInfo userInfo) {
...
}
当然如果你觉得上述还是满足不了你的需求,那么还可以自定义实现KeyGenerator,然后在@Cacheable注解中,使用keyGenerator进行指定,其中keyGenerator为beanName
@Cacheable(cacheNames = "user",keyGenerator = "customKeyGenerator")
注意:无法同时指定key和keyGenerator,否则会抛出异常
2.1.3 同步器
当多线程访问方法时,默认情况下,很可能出现重复执行方法逻辑获取方法结果的情况,可能我们这个过程是耗时的,我们不希望出现重复计算结果的情况,那么这种情况下我们可以给@Cacheable设置一个boolean属性为true:sync,当设置了此属性为true后,那么只会有一个线程参与方法逻辑执行计算结果,其余线程阻塞直到结果被计算出来放至缓存当中。
@Cacheable("user-info",sync=true)
2.1.4 条件性缓存
在某些需求背景下,可能某个方法并不是一直适合走缓存,即走某些情况下进行缓存,某些情况下不进行缓存,为此,spring cache abstraction提供了condition属性,该属性使用SPEL表达式计算结果,若为true,则进行缓存,否则不进行缓存(也就是不管当前是否存在缓存,那么一定会发生真实的调用)。
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)
2.1.5 SpEL上下文
SpEL表达式进行就算时,都会依赖一个上下文。那我们在使用SPEL表达式时,也可以使用上下文去获取一些数据,大致有三种:
1)#root
通过#root可以获取到当前方法比如方法名称以及方法所在对象等等
#root.methodName 获取方法调用名称
#root.method.name
#root.target
#root.targetClass
#root.args[0]
#root.caches[0].name
2)#入参名称
#入参名称可以获取方法对应入参的传入值
3)#result
通过#result可以获取到方法调用结果对象
2.2 @CachePut (应用于更新方法上)
当在干扰方法执行需要去更新缓存的时候,可以使用@CachePut。被@CachePut标注的方法总是被执行具体逻辑,并将其返回结果放置到缓存当中,@CachePut支持和@Cacheable一样的选项属性。
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
注意:切勿在同一个方法上同时使用@CachePut和@Cacheable注解。
2.3 @CacheEvict(应用于删除数据方法)
当前有了@Cacheble(查询缓存)、@CachePut(更新缓存),那么还缺少一个删除缓存--@CacheEvict。被@CacheEvict标注的方法,在执行完成后(执行前还是执行后由beforeInvocation属性决定)会将对应的缓存删除。相比上面两注解,该注解多了两个属性:
- allEntries(默认false)
决定是否将所有缓存的数据删除,默认只删除注解通过catchName和key指定匹配到的缓存 - beforeInvocation(默认false)
决定删除缓存的动作发生在方法真实调用之前还是之后
2.4 @Caching
在某些情况,同一个方法在不同的入参或者配置的不同条件下可能要变现出不同的缓存行为,可以使用@Caching 注解进行组合。
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
2.5 @CacheConfig
@CacheConfig只能被标注类上,可以用来指定一些公共配置,比如指定该类所有的缓存行为的name等等,不用在每个方法头上的缓存注解上搞相同的配置。
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
三、自定义缓存注解
我们可以使用@Cacheable, @CachePut, @CacheEvict,以及 @CacheConfig作为元注解标注到我们自定义的注解中,将缓存行为传递到我们自己定义注解中。
- 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames = "user",keyGenerator = "customKeyGenerator")
public @interface UserCacheAnnotation {
}
- 使用自定义注解
@UserCacheAnnotation
public Optional<UserInfo> getUserInfo(int userId) {
return userInfoRepository.findById(userId);
}
网友评论