缓存的基本概念
通常在 Web 应用发开中,不同层级对应的缓存策略和要求是不一样的,如下:
以下是缓存中的2个比较重要的基本概念:
- 缓存命中率
缓存命中率即从缓存中读取数据的次数和总读取次数的比例,一般来说,命中率越高越好:命中率 = 从缓存中读取的次数 / 总读取次数Miss 率 = 没有从缓存中读取的次数 / 总读取次数※总读取次数 = 从缓存中读取的次数 + 从慢速设备中读取的次数
- 缓存过期策略
如果缓存满了,缓存中移除数据的策略,常见的策略有: LFU、LRU、FIFO 等;
- FIFO(First In First Out):先进先出策略,先放入缓存的数据先被移除;
- LRU(Least Recently Used):最久未使用策略,使用距离现在最久的那个数据被移除;
- LFU(Least Frequently Used):最近最少使用策略,一定时间内使用次数最少的那个数据被移除;
- TTL(Time To Live):固定存活期策略,从缓存中创建时间点开始直到到期的一个时间段,不管在该时间是否被访问否将过期;
- TTI(Time To Idle):固定空闲期策略,一个数据在指定的时间内没有被访问,就会从缓存中被移除;
Spring 4.0 开始全面支持 JSR-107(JCache API),提供了一种可以在方法级别进行缓存的缓存抽象,主要的实现过程是在缓存注解类中进行相关方法的标记,Spring 通过 AOP 功能为其生成具有缓存功能的代理类;
使用 Spring Cache 时有以下几点要注意:
- Spring Cache 提供的仅仅是一种抽象,而没有提供具体实现,所以一般会自己使用 AOP 来进行一定程度的封装实现(或者使用第三方的缓存框架);
- Spring Cache 并不针对多进程的应用环境进行专门的处理,即当应用程序处于分布式或集群环境下时,需要针对具体的缓存进行相应的配置;
- Spring Cache 抽象的操作中并没有锁的概念,当多线程并发操作同一个缓存项时,将可能得到过期的数据,此时可以自己实现或借助第三方缓存框架来实现并发锁;
Spring Cache 缓存处理
一个简单的使用示例
Spring Cache 的基本使用主要包括以下2个步骤:
- 定义缓存:确定需要缓存的方法和缓存策略;
- 缓存配置:配置缓存信息;
在业务层对象 UserService 使用注解标注缓存行为:
package site.assad.service;
public class UserService {
private UserDao userDao;
private final static Logger log = LogManager.getLogger();
//标记方式使用缓存,当调用该方法时,会先从 users 缓存中匹配查询缓存对象,如果匹配则直接返回缓存对象,否则执行方法内的逻辑
//在这个方法中,对应浑春的 key 为 userId 的值,value 为 userId 对应的 User 对象;
cacheNames = "users") (
public User getUser(final int userid){
log.debug("real query user from DB");
//不考虑缓存逻辑,直接实现业务
User user = null;
user = userDao.getUserById(userid);
return user;
}
}
在Spring上下文配置文件 applicationContext.xml 中配置 Spring Cache:
<!--Spring Cache 配置-->
<!--启动基于注解的缓存驱动,该驱动会默认使用一个定义为“cacheManager”的缓存管理器-->
<cache:annotation-driven />
<!--指定使用缓存注解的类,多个类可以使用内嵌的list节点包含-->
<bean id="accountServiceBean" class="site.assad.service.UserService" />
<!--配置缓存管理器,使用默认实现SimpleCacheManager-->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<list>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" />
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users" />
</list>
</property>
</bean>
缓存注解
Spring 4.X 定义了4个可在方法级别、1个类级别上使用的注解,用于定义在某方法上的返回值会被缓存、或者从缓存中移除;
@Cacheable
这两个注解用于定制被注解的方法的返回值是可以被缓存的,工作原理是 Spring 首先从缓存中查找数据,如果没有则执行方法并缓存结果,再返回数据;
@Cacheable 拥有的属性如下:
属性参数 | 说明 | 使用示例 |
value / cacheNames | 缓存的名称,在 Spring 配置文件中定义,必须指定; | @Cacheable(cacheNames = "users") 或标明多个参数时 : @Cacheable(cacheNames = {"users",“members”}) |
key | 缓存的 key,可以为空; 如果指定,则按照 SpEL 表达式编写; 如果没有指定,则默认按照所有参数进行组合; | @Cacheable(cacheNames = "users",key="#userId") |
condition / unless | condition 为缓存的条件,可以为空,使用 SpEL 表达式进行编写; unless 与 condition 相反,为不为缓存的条件; | @Cacheable(cacheNames = "users",condition="#userAge>20") |
使用示例如下:
cacheNames = "users",key = "#userId") (
public User getUser(final int userid){
log.debug("real query user from DB");
User user = null;
user = userDao.getUserById(userid);
return user;
}
缓存的本质是 key/value 集合,默认情况下,缓存抽象使用方法签名和参数值作为一个键值,并将该键与方法调用的结果组成 key/value,如果在 Cache 注解上没有指定 key,Spring 会使用默认的 KeyGenerator 来生成一个key,在 Spring 4.X ,该默认的构造器为 SimpleKeyGenerator ,如果需要使用到自定义的键构造器,可以实现 KeyGenerator 接口来实现一个键构造器,然后在 keyGenerator 中指定该构造器,如下:
@Cacheable(cacheNames = "users",keyGenertor="myKeyGenerator")
@CachePut
和 @Cacheable 类似,用于指定被注解的方法的返回值可以被缓存,但是
这两个注解不能同时用在用一个方法上,这2个注解的区别是:@Cacheable 可以跳过方法直接获取缓存,@CahePut 则会强制执行方法以更新注解;
除此之外,@CachePut 的参数和 @Cacheable 一样,都拥有 value/cacheNames、key、condition 等属性;
@CacheEvict
@CacheEvict 是 @Cacheable 的方向操作,即从给定的缓存中移除一个值,该注解通常用在更新或删除用户的操作;
大多数缓存框架都提供了缓存数据的有效期,使用该注解可以显式从缓存中删除失效的缓存数据;
@CacheEvict 除了拥有 @Chacheable 的属性 value/chaceNames、key、condition 之外,还拥有以下属性:
属性参数 | 说明 |
allEntries | 标记是否需要清空缓存中的所有元素,默认为 false; 如果为true,Spring 将忽略指定的 key,清除缓存中的所有内容; |
beforeInvocation | 标记清除缓存操作的执行时间点,默认为 false; 一般清除操作默认实在执行方法成功后触发的,该注解可以重新指定清除操作的执行时间点; 当为 true 时,Spring 会在调用方法之前清除缓存中的指定元素; |
一个示例的使用如下:
cacheNames = "users",key="#userId") (
public void removeUserFromCache(int userId){
userDao.removeUserById(userId);
log.debug("remove user "+ userId + " from cache");
}
@Caching
@Caching 是一个组注解,可以为一个方法提供定义基于 @Cacheable/@CachePut、@CacheEvict 注解的数组,如下使用示例:
public class UserService {
cacheable = { (
value = "members", condition = "#obj instanceof T(site.assad.domain.Member)"), (
value = "visitors", condition = "#obj instanceof T(site.assad.domain.Visitor)") (
})
public User getUser(User obj) {
return userDao.getUser(obj);
}
}
以上示例中,Member 和 Visitor 都是 User 的子类, 对于 getUser 方法的返回结果,根据返回类型的判定,将缓存到 memebers,visitors 两个不同的缓存中;
@CacheConfig
@CacheConfig 是 Spring 4.0 新增加的缓存注解,用于定义类级别的全局缓存,用于定义在类中重复使用的默认Cache注解,如下:
cacheNames = "users",key="#userId") (
public class UserService {
public User findA(User user) {
.....
}
public User findB(User user) {
.....
}
}
在以上的示例代码中 ,@CacheConfig 在类级别定义了缓存 users 、key,在 findA、findB 方法中使用@Cacheable中不指定参数,会默认使用 @CahceConfig 中的参数;
缓存注解中 SpEL 表达式的使用
Spring Cache 缓存注解属性 key、condition、unless 中,指定值是使用 SpEL 表达式的,SpEL 表达式可以基于上下文并通过使用缓存抽象,提供与 root 对象相关联的缓存特定参数,如下:
名称 | 位置 | 说明 | 示例 |
methodName | root 对象 | 当前被调用的方法名 | #root.methodName |
method | root 对象 | 当前被调用的方法 | #root.method.name |
target | root 对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root 对象 | 当前被调用的目标对象的Class对象 | #root.targetClass |
args | root 对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root 对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
Argument Name | 执行上下文 | 当前被调用的方法的参数 | @Cacheable(value = "users" , key = "#userId") public User getUser(final int userId){ ....... } @Cacheable(value = "users" , key = "#user.id") public User getUser(final User user){ ....... } |
result | 执行上下文 | 方法执行后的返回值(仅仅当方法执行后的判断有效,如 boforeInvocation=false) | #result |
缓存管理器
CacheManager 是提供了访问缓存名称和缓存对象方法、提供管理缓存、操作缓存、移除缓存方法具体实现的 SPI(Service Provider Interafce,服务提供程序接口);
Spring Cache 框架对于各种不同的需求,提供了不同的缓存管理器实现;
SimpleCacheManager
SimpleCacheManager 是缓存管理器的简化版本,可以配置缓存列表,并利用这些缓存列表进行相关的操作;
以下实例配置中利用 ConcurrentMapCacheFactoryBean 类来对 ConcurrentMapCache 进行实例化;
<cache:annotation-driven />
<bean id="accountServiceBean" class="site.assad.service.UserService" />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<list>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" />
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users" />
</list>
</property>
</bean>
NoOpCacheManager
NoOpCacheManager 主要用于测试目的,该缓存管理器实际上不缓存任何数据,示例配置如下:
<bean id="cahceManager" class="org.springframework.cache.support.NoOpCacheManager" />
ConcurrentMapCacheManager
ConcurrentMapCacheManager 使用了 JDK 的 ConcurrentMap 管理缓存队列,于 SimpleCacheManager 类似,当不需要定义缓存,如下:
<bean id="cahceManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
CompositeCacheManager
当应用程序上下文中定义 <cache:annotation-driven /> 时,只能提供一个缓存管理器,使用 CompositeCacheManager 能够定义多个缓存管理器,同时 CompositeCacheManager 还可以通过 fallbackToNoOpCache 属性放hi NoOpCacheManager ;
以下示例中通过 CompositeCacheManager ,及拿过一个 SimleCacheManager 和 一个 HazelCast 缓存管理器宽版在一起,SimleCacheManager 管理 members 缓存,HazelCast 管理 visitors 缓存;
<cache:annotation-driven />
<bean id="accountServiceBean" class="site.assad.service.UserService" />
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<!--定义SimpleCacheManager管理器-->
<bean class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<list>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="members" />
</list>
</property>
</bean>
<!--定义HazelCast管理器-->
<bean class="com.hazelcast.spring.cache.HazelcastCacheManager">
<constructor-arg ref="hazelcast" />
</bean>
</list>
</property>
</bean>
使用基于 XML 的 Cache 声明
在某些情况下,由于某些原因无法获取项目的源码,可以使用 XML 的方式配置 Spring Cache,配置方式于 transaction 管理器的 advice 类似,如下:
<!--需要定义缓存的类-->
<bean id="userService" class="site.assad.service.UserService" />
<!--缓存增强定义-->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="getUser" key="#userId" />
<cache:cache-evict method="removeUser" key="#userId"/>
</cache:caching>
</cache:advice>
<!--缓存切面定义-->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(*site.assad.service.UserService.*(..))" />
</aop:config>
常用Cache技术的集成配置
对于常用的第三方缓存框架,Spring 提供了对他们的集成机制;
EhCache
EhCache 是广泛使用的 Java 开源缓存框架之一,使用 EhCache ,需要在项目中导入以下依赖:
net.sf.ehcach:ehcahe
在 Spring 上下文配置文件 applicationContext 文件中的配置如下:
<cache:annotation-driven/>
<context:component-scan base-package="site.assad.service"/>
<!--配置EhCacheCacheManager缓存管理器-->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:ehcache.xml"/>
之后在 EhCache 配置文件中中配置缓存bean,ehcache.xml
<ehcache>
<cache name="users" maxElementsInMemory="1000" />
</ehcache>
Guava
Guava 是 Google 的一个开源公共库,他提供了缓存的功能,需要导入以下依赖:
com.google.guava:guava
一个简单的配置如下,并不需要定义缓存,因为它们将在被需要时被创建;
<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager" />
HazelCast
HazelCast 是流行的 Java 分布式内存网格框架之一,使用 HazelCast 需要导入以下依赖:
com.hazelcast:hazelcast-all
在 Spring 上下文定义 HazelCast 缓存管理器如下:
<cache:annotation-driven/>
<context:component-scan base-package="com.smart.cache.hazelcast"/>
<!--定义HazelCast Bean-->
<hz:hazelcast id="hazelcast">
<hz:config>
<hz:map name="users">
<hz:map-store enabled="true" class-name="site.assad.domain.User" write-delay-seconds="0"/>
</hz:map>
</hz:config>
</hz:hazelcast>
<!--定义HazelCast缓存管理器-->
<bean id="cacheManager" class="com.hazelcast.spring.cache.HazelcastCacheManager">
<constructor-arg ref="hazelcast" />
</bean>