解决springCache配置中踩的坑

解决springCache配置中踩的坑

springCache配置中踩的坑

项目基于SpringBoot,使用了SpringCache。

早先在网上找了一份SpringCache的配置,后来由于需要使用到自定义序列化方法,注入一个自定义的序列化类。但是在后来发现自定义的序列化类始终没有调用,后来查看源码后终于发现了原因

先附上正确的配置

  @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory, SessionSerializer serializer) {
        logger.debug("生成缓存管理器");
        logger.debug("注入的序列化工具={}", serializer);
        RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(serializer);
        logger.debug("生成的cache序列化工具={}", pair);
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();  // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        config = config.entryTtl(Duration.ofMinutes(10))     // 设置缓存的默认过期时间,也是使用Duration设置
                .serializeValuesWith(pair)
//                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .disableCachingNullValues()
        ;     // 不缓存空值
        logger.debug("初始化完成的config={}", config);
        // 设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add(CACHE_NAME);
        return RedisCacheManager.builder(new CusTtlRedisCacheWriter(factory))     // 使用自定义的缓存配置初始化一个cacheManager
                .cacheDefaults(config)//这一句必须要最先执行,否则实际运行时使用的是defaultConfig
                .initialCacheNames(cacheNames)
//                .withInitialCacheConfigurations(configMap)
//                .transactionAware()
                .build();
    }

重要在于最后一行return的时候,早先的找到的资料说initialCacheNames方法一定要先执行,否则就会巴拉巴拉~~~,,结果就掉坑了

如果initialCacheNames方法先执行的话,实际上CacheManager里使用的是DefaultConfig,里面的序列化方式也就是Jdk序列化,后面在调用cacheDefaults也没有用了。

所有,cacheDetaults方法一定要先执行

springCache配置及一些问题的解决

配置

1. applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cache="http://www.springframework.org/schema/cache"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
  
    <cache:annotation-driven />
  <!-- 定义缓存管理 -->
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                      p:name="default"/>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                      p:name="activityCache"/>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                      p:name="awardsCache"/>
            </set>
        </property>
    </bean>

Spring内部默认使用 ConcurrentHashMap 来存储, 配置中的 activityCache awardsCache 就是一个一个的 ConcurrentHashMap 对象的名字. 另外 spring还支持使用 EHCache 来存储缓存.

2. 在service的实现类上加上 @Cacheable

//@Service
//public class LotteryActivityServiceImpl implements LotteryActivityService
@Cacheable(value = "activityCache", key = "#shortName")
public LotteryActivity findByShortName(String shortName) {
    log.info("query activity : {} from database.", shortName);
    return activityRepository.findByShortName(shortName);
}

@Cacheable参数

value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

在需要清除缓存的方法上加上@CacheEvict

@CacheEvict(value="activityCache", allEntries = true, beforeInvocation = true)
public void cleanActivityCache(String shortName) {
    log.info("cleaned cache activity : {}", shortName);
}

@CacheEvict 参数

value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如: @CachEvict(value=”testcache”,key=”#userName”
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”)
allEntries 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)

当需要保证方法被调用,又希望结果被缓存, 可以使用@CachePut

@CachePut(value="accountCache",key="#account.getName()")// 更新 accountCache 缓存
 public Account updateAccount(Account account) { 
   return updateDB(account); 
 } 

@CachePut 参数

value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

注解最好加在实现类而不是接口的方法上

Spring recommends that you only annotate concrete classes (and methods of concrete classes) with the @Cache* annotation, as opposed to annotating interfaces. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), then the caching settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a caching proxy, which would be decidedly bad.

带有@Cache* 注解的方法不能被定义在调用该方法的类里, 比如 UserController要调用 findUserByName(String name), 且该方法有 @Cacheabele注解, 那么该方法就不能被定义在 UserController中

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual caching at runtime even if the invoked method is marked with @Cacheable - considering using the aspectj mode in this case.

@Cache*注解要加在 public 方法上

When using proxies, you should apply the @Cache* annotations only to methods with public visibility. If you do annotate protected, private or package-visible methods with these annotations, no error is raised, but the annotated method does not exhibit the configured caching settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods as it changes the bytecode itself.

以上为个人经验,希望能给大家一个参考,也希望大家多多支持云海天教程。