Spring Cache

Posted 默默的看雨下

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cache相关的知识,希望对你有一定的参考价值。

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





以上是关于Spring Cache的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cache

Spring Cache

Spring Cache 缓存

Yii2片段缓存详解

Spring Boot (24) 使用Spring Cache集成Redis

Spring Cache 缓存