Spring 的 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 增加的配置项,具体如下:
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> <!-- (redis 数据源、Spring-redis 连接池管理工厂、redis template definition、将 redisTemplate 封装成通用服务,等) --> <!-- . . . --> <!--启用缓存注解功能,这个是必须的,否则注解不会生效 --> <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>
<cache:annotationdriven/> 的属性
如果使用了 <cache:annotation-driven />,还需要配置一个名为 CacheManager 的缓存管理器 Spring Bean。
- cache-manager 属性:用来指定所需要用到的缓存管理器(CacheManager)的 Spring Bean 的名称。
- 如果不进行特别设置,默认的名称是 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”模式:【默认】
- proxy-target-class 属性:设置代理类的创建机制,有两个值:
- 值为“true”:
- 表示使用 CGLib 创建代理类;
- @Cacheable 和 @CacheInvalidate 等注解,必须标记在具体类(Concrete Class)上,不能标记在接口上,否则不会发生作用。
- 值为“false”:【默认】
- 表示使用 JDK 的动态代理机制创建代理类。
- @Cacheable 和 @CacheInvalidate 等注解,可以标记在接口上,也能发挥作用。
- 值为“true”:
1、JDK 的动态代理是利用反射机制生成一个实现代理接口的匿名类(Class-based Proxies),在调用具体方法前,通过调用 InvokeHandler 来调用实际的代理方法。 2、而使用 CGLib 创建代理类,则不同。CGLib 底层采用 ASM 开源 .class 字节码生成框架,生成字节码级别的代理类(Interface-based Proxies)。 对比来说,在实际的运行时,CGLib代理类比使用Java反射代理类的效率要高。【参见:动态代理、CGLIB 与 切面编程】
使用 RedisTemplate 模板 API 完成 CRUD 的实践案例
简单介绍一下Spring的三个缓存注解:@CachePut、@CacheEvict、@Cacheable,通常都加在方法的前面,大致的作用如下:
- @CachePut:作用是设置缓存。
- 先执行方法,并将执行结果缓存起来。
- @Cacheable:作用更多是查询缓存。
- 首先检查注解中的 Key 键是否在缓存中,如果是,则返回 Key 的缓存值,不再执行方法;否则,执行方法并将方法结果缓存起来。
- (从后半部分来看,@Cacheable 也具备 @CachePut 的能力)
- @CacheEvict:作用是删除缓存。
- 在执行方法前,删除缓存。
示例:
- 实现一个带缓存功能的用户操作类 UserServiceImplWithAnno(UserService 的实现类,其功能和前面介绍的 UserServiceImplWithTemplate 一样):
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); } }
如上:在 UserServiceImplWithAnno 类中没有出现任何的缓存操作 API,但是其缓存功能和前面的 UserServiceImplWithTemplate 类使用 RedisTemplate 模板实现的缓存功能,是一模一样的。
使用缓存注解,能较大地【减少代码量】,还能方便地【在不同的缓存服务之间实现切换】。