Spring Cache
Spring提供一套对外一致的Cache API(Cache抽象),底层Cache的实现和Cache缓存策略由开发者自行维护。Spring Cache提供Cache、CacheManager接口来进行对Cache的抽象,提供 @Cacheable、@CachePut等注解来进行对Cache的使用。
Spring Cache用于代码级别的缓存,其一般用在service层。在Dao层一般使用ORM缓存,例如Mybatis缓存。在考虑底层实现时,需要根据具体业务来选择不同的实现,简单的总结如下:
缓存实现 | 优势 | 劣势 |
---|---|---|
ConcurrentMapCache | 简单易用、不需要任何的配置 | 无缓存策略、不支持集群 |
EhCache | 高性能、高效率、支持多种缓存配置 | 需要配置ehcache策略、不支持集群 |
Redis | 与平台无关的缓存数据库,支持集群,有许多优势(百度一下,你就知道了) | 与Java平台无关,访问需要socket通信 |
由上可见,我们一般在非集群环境下使用EhCahe,集群环境下使用Redis、Memcached等缓存,具体百度。
Spring Cache 抽象
Spring实现了以下Cache的抽象,部分Cache实现了对Spring事务的支持,即事务回滚时缓存也回滚,下面除了GuavaCache外都实现了对Spring事务的支持:
- EhCache:底层Cache由EhCache实现
- ConcurrentMapCache:底层由ConcurrentMap实现
- GuavaCache:底层Cache由Google Guava实现
- CaffeineCache:底层Cache由caffeine Cache实现
- JCacheCache:底层Cache由javax.cache.Cache实现
还有其他开源项目也提供了Spring Cache的实现,例如Spring-boot-starter-data-redis提供了Redis Cache的实现。
Spring Cache、CacheManager接口API:
public interface Cache {
//缓存的名字
String getName();
//得到底层使用的缓存,如Ehcache
Object getNativeCache();
//根据key得到一个ValueWrapper,然后调用其get方法获取值
ValueWrapper get(Object key);
<T> T get(Object key, Class<T> type);
void put(Object key, Object value);
void evict(Object key);
void clear();
interface ValueWrapper { //缓存值的Wrapper
Object get(); //得到真实的value
}
}
public interface CacheManager {
//根据Cache名字获取Cache
Cache getCache(String name);
Collection<String> getCacheNames();
}
Spring Cache API使用:
@Test
public void testEhcache() throws IOException {
User user = new User("kanyuxia", "123456");
net.sf.ehcache.CacheManager ehCacheManager =
new net.sf.ehcache.CacheManager(new ClassPathResource("ehcache.xml").getInputStream());
CacheManager cacheManager = new CacheManager(ehCacheManager);
Cache cache = cacheManager.getCache("user");
cache.put(user.getUsername, user);
Assert.assertEquals(user, cache.get(user.getUsername, User.class));
}
Spring Cache注解
Spring中主要基于注解+AOP来操作缓存,这样使得使用缓存比较方便但也有一定的局限性,下面是Spring Cache的几个主要注解:
- @Cacheable:该注解主要用于查询方法上,每次在调用方法时会先从缓存中去取,如果取不到才会执行方法,并把方法结果放入相应的缓存中。
- @CachePut:该注解主要用于插入、更新方法上,该方法会直接把方法调用结果放入相应的缓存中。
- @CacheEvit:该注解主要删除方法上。该方法会删除相应的缓存。
- @Caching:该注解主要用于在方法上需要多个注解,其是多个注解的组合。
- @CacheConfig:该注解在Spring 4.1后有效,配置在Class上,它为该类上的所有Cache注解提供一系列默认的配置。
上诉三个注解的参数信息如下:
- value、cacheNames:用户配置缓存名称,可配置多个。
- key:缓存的Key,可以使用Spring EL表达式配置。
- keyGenerator:缓存Key生成器,如果没有配置Key,则会自动调用生成Key。
- condition、unless:表示执行缓存行为的条件,可以使用Spring EL表达式配置。
- beforeInvocation:在 @CachePut、@CacheEvit中使用,表示在方法前执行缓存行为,默认为false。
- allEntries:在 @CacheEvit中使用,表示缓存中的所有Key。
在key、condition等中可以使用Spring EL表达式,可以更好的在其中写逻辑,下面是Spring Cache提供我们使用的Spring EL表达式:
方法名 | 位置 | 实例 | 描述 |
---|---|---|---|
methodName | root对象 | #root.methodName | 当前方法名 |
method | root对象 | #root.method | 当前方法 |
target | root对象 | #root.target | 当前调用该方法的对象 |
args | root对象 | #root.args | 当前方法的参数数组 |
argument name | 执行上下文 | #id | 当前方法的参数 |
result | 执行上下文 | #result | 当前方法的返回值,当且仅当缓存在方法调用后执行才有用 |
下面是简单的使用:
@CachePut(cacheNames = "cache", key = "#result.id")
public CacheEntity addCache(String name) {
// 省略处理逻辑
return cacheEntity;
}
@Cacheable(cacheNames = "cache", key = "#id")
public CacheEntity selectCacheById(Long id) {
return cacheMapper.selectById(id);
}
@CacheEvict(cacheNames = "cache", key = "#id", condition = "#result eq true")
public boolean deleteCacheById(Long id) {
return cacheMapper.deleteById(id);
}
@Caching(
put = {
@CachePut(),
@CachePut()
}
)
public void testCaching() {
}
Spring Cache注解相关的问题:
- 不能够自定义缓存值: Spring @CachePut、@Cacheable会自动把返回值放入缓存中,不能够自定义的放入想要的值。
- 动态代理带来缓存失效问题: Spring Cache使用AOP+注解进行缓存操作,如果我们使用动态代理且方法是内部调用或者非pulic方法则会出现缓存失效问题,例如:
public void internalCall() {
this.internalCall("hello");
}
@Cacheable(cacheNames = "cache", key = "#key")
public String internalCall(String key) {
System.out.println("Execute Internal Call Method");
return "world";
}
解决方法:使用AspectJ进行AOP处理。
Spring Cache使用
- Spring项目
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcacheManager"/>
<property name="transactionAware" value="true"/>
</bean>
<cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/>
- Spring Boot项目
Spring Boot项目需要在Configuration类上加 @EnableCache注解,然后在yml文件中选择实现哪种类型的CacheManager,之后Spring Boot会自动注入相关CacheManager。
Spring Cache问题
在使用Spring Cache和Shiro时,出现了Cache注解失效问题。主要是因为Shiro会使用AOP代理对象,然后Spring Cache也会使用AOP代理对象,由于先使用Shiro代理会出现Spring Cache代理失败问题,所以我们需要先Cache代理然后再Shiro代理。在自定义Realm时注入的service加上 @Lazy注解就可以使得Shiro后于Cache代理。代码如下:
@Lazy
@Autowired
private UserService userService;
Reference
http://jinnianshilongnian.iteye.com/blog/2001040
http://blog.chenfu3991.com/post/ehcache-diff-redis.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
https://stackoverflow.com/questions/21512791/spring-service-with-cacheable-methods-gets-initialized-without-cache-when-autowi