查看“Spring 的 Redis缓存注解”的源代码
←
Spring 的 Redis缓存注解
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:Redis]] == 关于 == 前面讲的 Redis 缓存实现都是基于 Java 代码实现的。在 Spring 中,通过合理的添加缓存注解,也能实现和前面示例程序中一样的缓存功能。 为了方便地提供缓存能力,Spring 提供了一组缓存注解。但是,'''这组注解不仅仅是针对 Redis,它本质上并不是一种具体的缓存实现方案(例如:Redis、EHCache等),而是对缓存使用的统一抽象'''。 * 通过这组缓存注解,然后加上与具体缓存相匹配的 Spring 配置,不用编码就可以快速达到缓存的效果。 == 配置 spring-redis.xml == 在使用 Spring 缓存注解前,首先需要配置文件中启用 Spring 对基于注解的 Cache 的支持: : 在“'''spring-redis.xml'''”中,加上“'''<cache:annotationdriven/>'''”配置项。 * 还需要配置一个名为 CacheManager 的'''缓存管理器''' Spring Bean。 '''spring-redis.xml''' 增加的配置项,具体如下: : <syntaxhighlight lang="xml" highlight=""> <!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> <!-- 1、redis 数据源 2、Spring-redis 连接池管理工厂 3、redis template definition --> <!-- . . . --> <!--启用缓存注解功能,这个是必须的,否则注解不会生效 --> <cache:annotation-driven/> <!--自定义 redis 工具类,在需要缓存的地方注入此类 --> <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"> <constructor-arg ref="redisTemplate"/> <constructor-arg name="cacheNames"> <set> <!--声明 userCache--> <value>userCache</value> </set> </constructor-arg> </bean> </syntaxhighlight> === <cache:annotationdriven/> 的属性 === 不同的 spring-data-redis 版本,构造函数不同,这里使用的版本是 1.4.3。 对于 2.0 版本,在配置上发生了一些变化,但是原理是大致相同的。 # '''cache-manager 属性''':用来指定所需要用到的'''缓存管理器'''(CacheManager)的 Spring Bean 的名称。【'''使用“<cache:annotation-driven/>”必须的属性'''】 #* 如果不进行特别设置,默认的名称是 CacheManager。 #* 设置的这个 Bean,要求实现 CacheManager 接口——CacheManager 接口是 Spring 定义的一个用来管理 Cache 缓存的通用接口。对应于不同的缓存,需要使用不同的 CacheManager 实现。 #** Spring 自身已经提供了一种 CacheManager 的实现,是基于 Java API 的 ConcurrentMap 简单的内存 Key-Value 缓存实现。 #** 但是,这里需要使用的缓存是 Redis,所以使用 spring-data-redis 包中的 '''RedisCacheManager''' 实现。 #* 在配置 RedisCacheManager 缓存管理器 Bean 时,需要配置两个构造参数:(如上示例) #*# redisTemplate:模板 Bean。 #*# cacheNames:缓存名称。【???】 #*#* 在 Redis 中其实际上即为命名空间,在 Redis 中体现为 key 的前缀“<cacheName>::”。【默认使用“'''::'''”作为命名空间的分隔符(即 )】 #*#* 系统可以配置多个 cacheName 用以区分不同业务的 key 的存储位置(命名空间)。 # '''mode 属性''':可选值有 proxy 和 aspectj。 ## “'''proxy'''”模式:【默认】 ##* 只有当有缓存注解的方法被对象外部的方法调用时,Spring Cache 才会发生作用(反过来说,如果一个缓存方法,被其所在对象的内部方法调用时,Spring Cache 是不会发生作用的); ##* 只有加在 public 类型方法上的 @Cacheable 等注解,才会让 Spring Cache 发生作用。 ## “'''aspectj'''”模式: ##* 只有注解方法被内部调用,缓存才会生效; ##* 非 public 类型的方法上,也可以使用 Spring Cache 注解。 # '''proxy-target-class 属性''':设置代理类的创建机制,有两个值: ## 值为“'''true'''”: ##* 表示使用 '''CGLib''' 创建代理类; ##* @Cacheable 和 @CacheInvalidate 等注解,必须标记在具体类(Concrete Class)上,不能标记在接口上,否则不会发生作用。 ## 值为“'''false'''”:【默认】 ##* 表示使用 JDK 的'''动态代理'''机制创建代理类。 ##* @Cacheable 和 @CacheInvalidate 等注解,可以标记在接口上,也能发挥作用。 1、JDK 的动态代理是利用反射机制生成一个实现代理接口的匿名类(Class-based Proxies),在调用具体方法前,通过调用 InvokeHandler 来调用实际的代理方法。 2、而使用 CGLib 创建代理类,则不同。CGLib 底层采用 ASM 开源 .class 字节码生成框架,生成字节码级别的代理类(Interface-based Proxies)。 对比来说,在实际的运行时,CGLib代理类比使用Java反射代理类的效率要高。【参见:[[动态代理、CGLIB 与 切面编程]]】 == 使用 Spring 缓存注解完成 CRUD 的实践案例 == 简单介绍一下Spring的三个缓存注解:@CachePut、@CacheEvict、@Cacheable,通常都加在方法的前面,大致的作用如下: # @CachePut:作用是设置缓存。 #* 先执行方法,并将执行结果缓存起来。 # @Cacheable:作用更多是查询缓存。 #* 首先检查注解中的 Key 键是否在缓存中,如果是,则返回 Key 的缓存值,不再执行方法;否则,执行方法并将方法结果缓存起来。 #:(从后半部分来看,@Cacheable 也具备 @CachePut 的能力) # @CacheEvict:作用是删除缓存。 #* 在执行方法前,删除缓存。 示例: : 实现一个带缓存功能的用户操作类 UserServiceImplWithAnno(UserService 的实现类,其功能和前面介绍的 UserServiceImplWithTemplate 一样): : <syntaxhighlight lang="Java" highlight=""> package com.crazymakercircle.redis.springJedis; import com.crazymakercircle.im.common.bean.User; import com.crazymakercircle.util.Logger; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service @CacheConfig(cacheNames = "userCache") public class UserServiceImplWithAnno implements UserService { public static final String USER_UID_PREFIX = "'userCache:'+"; /** * CRUD 的创建/更新 * @param user 用户 */ @CachePut(key = USER_UID_PREFIX + "T(String).valueOf(#user.uid)") @Override public User saveUser(final User user) { // 保存到数据库 ... Logger.info("user : save to redis"); return user; } /** * CRUD 的查询 * @param id id * @return 用户 */ @Cacheable(key = USER_UID_PREFIX + "T(String).valueOf(#id)") @Override public User getUser(final long id) { // 从数据库中加载 User user = ...; Logger.info("user : is null"); return user; } /** * CRUD 的删除 * @param id id */ @CacheEvict(key = USER_UID_PREFIX + "T(String).valueOf(#id)") @Override public void deleteUser(long id) { // 从数据库中删除 ... Logger.info("delete User:", id); } } </syntaxhighlight> 如上:在 UserServiceImplWithAnno 类中没有出现任何的缓存操作 API,但是其缓存功能和前面的 UserServiceImplWithTemplate 类使用 RedisTemplate 模板实现的缓存功能,是一模一样的。 使用缓存注解,能较大地【'''减少代码量'''】,还能方便地【'''在不同的缓存服务之间实现切换'''】。 == 缓存注解详解 == Spring 框架自身并没有实现缓存解决方案,但是从 3.1 开始定义了 '''org.springframework.cache.Cache''' 和 '''org.springframework.cache.CacheManager''' 接口,提供对缓存功能的声明,能够与多种流行的缓存实现集成。 1、Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合; Cache 接口下 Spring 提供了各种 xxxCache 的实现,如:RedisCache、EhCacheCache、ConcurrentMapCache 等; 2、CacheManager 接口为缓存管理器规范,简单来说就是用于存放 cache。 Spring 默认也提供了一些列管理器的实现。 Spring 缓存抽象提供了 5 个注解用来声明缓存规则: # '''@CachePut''':执行方法,并缓存结果。 # '''@Cacheable''':能够根据方法的请求参数对其结果进行缓存,多用于查询。 # '''@CacheEvict''':清空缓存。 # '''@Caching''':能够同时应用多个缓存注解功能。 # '''@CacheConfig''':用于抽取缓存的公共配置。【仅类级别】 * 以上 5 个注解除了 @CacheConfig 注解是类级别的注解,其余 4 个注解在“类”和“方法”上均可以使用: *# 作用在类上:表示对该类下所有方法生效;(但用得较少) *# 作用在方法上:只对该方法生效;且只能用于 public 修饰的符方法,protected 或者 private 修饰的方法不适用(和配置项的“mode 属性”有关???)。 常用属性说明: # '''value''' 属性:指定 Cache 缓存的名称。 #* value 值表示当前 Key 键被缓存在哪个 Cache 上(相当于缓存 Key 所属的命名空间),对应于 Spring 配置文件中 CacheManager 缓存管理器的 '''cacheNames''' 属性中配置的某个 Cache 名称; #* 可以设置多个 Cache:配置 value 值为一个数组。(如:“value={userCache,otherCache1,otherCache2}”) #* '''cacheNames''' 为 value 的别名(Spring 4 新增)。 # '''key''' 属性:指定 Redis 的 Key 的值。 #* 当没有指定该属性时,Spring 将使用默认策略('''SimpleKeyGenerator''')生成 Key 键: #*# 如果方法没有给出参数,则返回 SimpleKey.EMPTY;【即:“key = new SimpleKey()”】 #*# 如果方法只给出一个参数,则返回该参数的值;【即:“key = 参数的值”】 #*# 如果方法给出了更多的参数,则返回包含所有参数的 SimpleKey;【即:“key = new SimpleKey(params)”】 #** 当使用默认策略时,相关的参数需要有有效的“hashCode()”和“equals()”方法。 #* 该属性支持 '''SpringEL''' 表达式。 # '''condition''' 属性:指定缓存的条件“什么情况缓存”。 #* 当没有指定该属性时,表示将缓存所有的结果; #* 该属性支持 '''SpringEL''' 表达式:当表达式的值为 true 时,表示进行缓存处理;否则不进行缓存处理。 # '''unless''' 属性:指定缓存的条件“什么情况不缓存”。 #* 需使用 '''SpringEL''' 表达式; #* 不同于 condition 属性,该条件是在函数被调用之后才做判断的,所以它可以通过对 result 进行判断。 # '''sync''' 属性:是否使用'''同步模式'''。 #* 在多线程环境中,可能会出现相同的参数的请求并发调用方法的操作,默认情况下,spring cache 不会锁定任何东西,相同的值可能会被计算几次,这就违背了缓存的目的。对于这些特殊情况,可以使用 sync 属性。 #* 若设置为 true:则只有一个线程在处于计算,而其他线程则被阻塞,直到在缓存中更新条目为止。 # '''keyGenerator''' 属性:指定 key 生成器。 #* 该属性与 key 属性是互斥的; #* 自定义的 key 生成器需要实现 '''org.springframework.cache.interceptor.KeyGenerator''' 接口,并使用该参数来指定: #*: <syntaxhighlight lang="Java" highlight=""> @Configuration public class MyCacheConfig { // 省略其他配置项 . . . // 自定义 keyGenerator @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } } </syntaxhighlight> # '''cacheManager''' 属性:用于指定使用哪个缓存管理器。 #* 只有当配置有多个缓存管理器时才需要使用。 # '''cacheResolver''' 属性:指定使用的缓存解析器。 #* 需通过 '''org.springframework.cache.interceptor.CacheResolver''' 接口来实现自己的缓存解析器,并用该参数指定。 #*: <syntaxhighlight lang="Java" highlight=""> </syntaxhighlight> === @CachePut === ---- @CachePut:用于'''增加/更新缓存''',每次执行方法后,会将结果存入指定缓存的 Key 键上。 * 如果作用于 void 方法上:仍然会向缓存中进行存储,不过键值对中的 value 为 null。 支持的属性: # '''value''' # '''key''' # '''condition''' # '''unless''' # '''sync''' # '''keyGenerator''' # '''cacheManager''' # '''cacheResolver''' Redis 的缓存都是“键-值对”(Key-Value Pair): 1、Redis 缓存的 Key 键即为 @CachePut 注解配置的 key 属性值,一般是一个字符串,或者是结果为字符串的一个 '''SpEL(SpringEL)''' 表达式。 2、Redis 缓存的 Value 值就是方法的返回结果'''经过序列化'''后所产生的序列化数据。 示例:只有当 user 的 id 大于 1000 时,才会进行缓存,缓存到“userCache”中: : <syntaxhighlight lang="Java" highlight=""> @CachePut(value = "userCache", key = "T(String).valueOf(#user.uid)", condition = "#user.uid>1000") public User cacheUserWithCondition(final User user) { // 保存到数据库 . . . // 返回值将保存到缓存 Logger.info("user : save to redis"); return user; } </syntaxhighlight> === @Cacheable === ---- @Cacheable:用于'''查询(更新)缓存''',只有当 key 在缓存中不存在的情况下才执行方法(并更新缓存)。 * 如果作用于 void 方法上:仍然会向缓存中进行存储,不过键值对中的 value 为 null。 * 与 @CachePut 注解: *# 相同:都具备增加缓存的能力。 *# 不同:@CachePut 在方法执行前不去进行缓存检查,无论之前是否有缓存,都会将新的执行结果加入到缓存中。 ** 需要注意的是,不要在一个方法上同时使用 @Cacheable 和 @CachePut。 支持的属性: # '''value''' # '''key''' # '''condition''' # '''unless''' # '''sync''' # '''keyGenerator''' # '''cacheManager''' # '''cacheResolver''' === @CacheEvict === ---- @CacheEvict:用于'''清除缓存'''。 支持的属性: # '''value''' # '''key''' # '''condition''' # '''unless''' # '''sync''' # '''keyGenerator''' # '''cacheManager''' # '''cacheResolver''' # '''allEntrie''' 属性:表示是否清除缓存中的所有 Key 键。(boolean 类型) ## false:(默认)表示不需要清除全部。 ## true:表示清空 value 名称属性所指向的 Cache 中所有的缓存。 ##* 这时候,所配置的 key 属性值已经没有意义,将被忽略。 ##* 用于需要全部清空某个 Cache 的场景,这比一个一个清除 Key 键,效率更高。 # '''beforeInvocation''' 属性:表示是否在方法执行前操作缓存。(boolean 类型) ## false:(默认)在对应方法成功执行之后,再触发清除操作; ##* 如果方法执行过程中,有异常抛出,或者由于其他的原因,导致线程终止,就不会触发清除操作。所以,通过设置为 true 属性来确保清理。 ## true:在执行注解的方法之前完成缓存的清除工作; 示例: : 在方法执行完成前,一次清除 Cache 名称为 userCache 中的所有的 Redis 缓存: : <syntaxhighlight lang="Java" highlight=""> /** * 删除userCache名字空间的全部缓存 */ @CacheEvict(value = "userCache", allEntries = true, beforeInvocation = true) public void deleteAll() { // 其它操作 ... } </syntaxhighlight> === @Caching === ---- @Caching 注解,是一个缓存处理的'''组合注解'''。 * 通过 @Caching,可以一次指定多个 Spring Cache 注解的组合。 @Caching 的组合能力,主要通过三个属性完成: # '''put''':指定一个或者多个 '''@CachePut''' 注解的组合,用于设置一个或多个 key 的缓存。 #* 如果指定多个 @CachePut 注解,则直接使用数组的形式。 #*:(即使用花括号,将多个 @CachePut 注解包围起来。) # '''cacheable''':指定一个或者多个 '''@Cacheable''' 注解的组合,用于查询一个或多个 key 的缓存,如果没有,则按照条件将结果加入缓存。 #* 如果指定多个 @Cacheable 注解,则直接使用数组的形式。 # '''evict''':指定一个或者多个 '''@CacheEvict''' 注解的组合,用于删除一个或多个 key 的缓存。 #* 如果指定多个 @CacheEvict 注解,则直接使用数组的形式。 在数据库中,往往需要进行外键的级联删除: 在删除一个主键时,需要将一个主键的所有级联的外键,通通都删掉。如果外键都进行了缓存,在级联删除时,则可以使用 @Caching 注解,组合多个 @CacheEvict 注解,在删除主键缓存时,删除所有的外键缓存。 示例: : 在更新一个用户时,需要删除与用户关联的多个缓存:用户信息、地址信息、用户的消息、等等: : <syntaxhighlight lang="Java" highlight=""> /** * 一个方法上,一次性加上三大类cache处理 */ @Caching(cacheable = @Cacheable(key = "'userCache:'+ #uid"), put = @CachePut(key = "'userCache:'+ #uid"), evict = { @CacheEvict(key = "'userCache:'+ #uid"), @CacheEvict(key = "'addressCache:'+ #uid"), @CacheEvict(key = "'messageCache:'+ #uid") } ) public User updateRef(String uid) { // 业务逻辑 ... return null; } </syntaxhighlight> === @CacheConfig === ---- @CacheConfig:用于抽取缓存的公共配置。【类级别】 * 允许共享:cacheNames、KeyGenerator、CacheManager 和 CacheResolver。 *: 如:“@CacheConfig(cacheNames="user")”,代表该类下的方法均使用这个 cacheNames。 * 如果方法上注解使用和了 @CacheConfig 相同的属性,则以方法上的为准。 支持的属性: <syntaxhighlight lang="Java" highlight=""> @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CacheConfig { String[] cacheNames() default {}; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; } </syntaxhighlight> # '''cacheNames''': # '''keyGenerator''': # '''cacheManager''': # '''cacheResolver''': === '''@EnableCaching''' === ---- @EnableCaching:开关性注解,在'''项目启动类'''或'''某个配置类'''上使用此注解后,则表示允许使用注解的方式进行缓存操作。【自 Spring 3.1 加入了该注解】 * 当在配置类(@Configuration)上使用 @EnableCaching 注解时,会触发一个 post processor,这会扫描每一个 spring bean,查看是否已经存在注解对应的缓存。 *: 如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的 bean 执行处理。 * 以上内容使用 xml 配置方法开启缓存注解功能,所以为使用此注解。 *: 如果使用了这个注解,那么就不需要在 XML 文件中配置:“<cache:annotation-driven/>”、“cache manager”等内容了。 如果没有引入任何缓存中间件的话,那么 Spring 缓存注解默认操作的就是本机的缓存;而如果引入了 Redis 这样的缓存中间件的话,Spring 缓存注解操作的就是 Redis 中的缓存了。 == 缓存注解中的 SpringEL 表达式 == 对应于加在方法上的缓存注解(如:@CachePut、@Cacheable),spring 提供了专门的上下文类 '''CacheEvaluationContext'''。 * 这个类继承于基础的方法注解上下文 '''MethodBasedEvaluationContext'''(而后者继承于,大家熟悉的标准注解上下文:'''StandardEvaluationContext''')。 * CacheEvaluationContext 的构造器如下: *: <syntaxhighlight lang="Java" highlight=""> class CacheEvaluationContext extends MethodBasedEvaluationContext { //构造器 CacheEvaluationContext(Object rootObject, //根对象 Method method, //当前方法 Object[] arguments,//当前方法的参数 ParameterNameDiscoverer parameterNameDiscoverer) { super(rootObject, method, arguments, parameterNameDiscoverer); } //....省略其他Spring源代码 } </syntaxhighlight> * 在配置缓存注解(@CachePut 等)的 Key 时,可以用到 CacheEvaluationContext 的 '''rootObject''' 根对象。 **(通过该根对象可以获取到的属性,如下表) {| class="wikitable" style="width:100%;" |+ style="caption-side:top;" | Spring EL 支持获取的属性 |- ! 属性名称 !! 说明 !! 示例 |- | colspan="3" style="text-align:center;" | '''通过 CacheEvaluationContext 的 rootObject 根对象能获取的属性''' |- | methodName || 当前被调用的方法名 | <syntaxhighlight lang="xml" inline>#root.methodName</syntaxhighlight> |- | method || 当前被调用的方法 | <syntaxhighlight lang="xml" inline>#root.method.name</syntaxhighlight> |- | target || 当前被调用的目标对象 | <syntaxhighlight lang="xml" inline>#root.target</syntaxhighlight> |- | targetClass || 当前被调用的目标对象类型 | <syntaxhighlight lang="xml" inline>#root.targetClass</syntaxhighlight> |- | args || 当前被调用方法的参数列表 | 获取当前被调用方法的第 0 个参数:<syntaxhighlight lang="xml" inline>#root.args[0]</syntaxhighlight> |- | caches || 当前被方法调用所使用的缓存集合 * 如:<syntaxhighlight lang="xml" inline>@Cacheable(value={"cache1","cache2"})</syntaxhighlight> 有两个 cache | 当前被方法调用的第 0 个 cache 名称:<syntaxhighlight lang="xml" inline>#root.caches[0].name</syntaxhighlight> |- | colspan="3" style="text-align:center;" | '''通过 EvaluationContext 能获取的属性''' |- | Argument name || 当前方法的参数名称 | <syntaxhighlight lang="xml" inline>#id</syntaxhighlight> 或 <syntaxhighlight lang="xml" inline>#a0</syntaxhighlight> * 也可以使用“#p0”或“#p<#arg>”作为别名。 |- | result || 方法返回的结果(要缓存的值)。 * 只有在“unless”、“@CachePut”(用于计算键)或“@CacheEvict(beforeInvocation=false)”中才可用; * 对于支持的包装器(如:Optional),“#result”引用的是实际对象,而不是包装器 | <syntaxhighlight lang="xml" inline>#result</syntaxhighlight> |} 如上,展示了: # 通过 '''CacheEvaluationContext''' 的 '''rootObject''' 根对象可以获取到的属性。 #* 在配置 key 属性时,如果用到 SpEL 表达式 root 对象的属性,也可以将“'''#root'''”省略,因为 Spring 默认使用的就是 root 对象的属性。 #*: 如: #*: <syntaxhighlight lang="Java" highlight=""> @Cacheable(value={"cache1", "cache2"}, key="caches[1].name") public User find(User user) { // 省略:查询数据库的代码 ... } </syntaxhighlight> # 除了访问 SpEL 表达式 root 对象,还可以访问当前方法的参数以及它们的属性。访问方法的参数有以下两种形式: ## 使用“'''#p<参数index>'''”形式访问方法的参数: ##: <syntaxhighlight lang="Java" highlight=""> // 访问第0个参数,参数id @Cacheable(value="users", key="#p0") public User find(Stringid) { // 省略:查询数据库的代码 ... } // 访问参数user的id属性 @Cacheable(value="users", key="#p0.id") public User find(User user) { // 省略:查询数据库的代码 ... } </syntaxhighlight> ## 使用“'''#<参数名>'''”形式访问方法的参数:【!常用】 ##: <syntaxhighlight lang="Java" highlight=""> // 使用“#参数名”的形式访问第0个参数的属性值,这里是id @Cacheable(value="users", key="#user.id") public User find(User user) { // 省略:查询数据库的代码 ... } </syntaxhighlight>
返回至“
Spring 的 Redis缓存注解
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息