spring-data-redis源码导读

1 篇文章 0 订阅
1 篇文章 0 订阅

PS:  spring-data-redis在springboot2.0版本时有一次较大的逻辑功能改动,当前文章基于springboot2.1.13版本

Spring缓存支持

Spring定义了org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口来统一不同缓存技术。

其中CacheManager是Spring提供的各种缓存技术抽象接口,内部使用Cache接口进行缓存的增删改查操作,我们一般不会直接和Cache打交道。

针对不同的缓存技术,Spring有不同的CacheManager实现类,定义如下表:

CacheManager

描述

SimpleCacheManager

使用简单的Collection存储缓存数据,用来做测试用

ConcurrentMapCacheManager

使用ConcurrentMap存储缓存数据

EhCacheCacheManager

使用EhCache作为缓存技术

GuavaCacheManager

使用Google Guava的GuavaCache作为缓存技术

JCacheCacheManager

使用JCache(JSR-107)标准的实现作为缓存技术,比如Apache Commons JCS

RedisCacheManager

使用Redis作为缓存技术

声明式缓存注解

Spring提供5个注解来声明缓存规则,如下表所示:

注解

说明

@Cacheable

方法执行前先看缓存中是否有数据,如果有直接返回。如果没有就调用方法,并将方法返回值放入缓存

@CachePut

无论怎样都会执行方法,并将方法返回值放入缓存

@CacheEvict

将数据从缓存中删除

@Caching

可通过此注解组合多个注解策略在一个方法上面

@CacheConfig

在类级别共享一些常见的缓存相关设置。

@Cacheable 、@CachePut 、@CacheEvict都有value属性,指定要使用的缓存名称,而key属性指定缓存中存储的键。

@Cacheable

这个注解含义是方法结果会被放入缓存,并且一旦缓存后,下一次调用此方法,会通过key去查找缓存是否存在,如果存在就直接取缓存值,不再执行方法。

参数

解释

cacheNames

缓存名称

value

缓存名称的别名

condition

Spring SpEL 表达式,判断条件用来确定是否缓存

key

SpEL 表达式,用来动态计算key

keyGenerator

Bean 名字,用来自定义key生成算法,跟key不能同时用

unless

SpEL 表达式,用来否决缓存,作用跟condition相反

sync

多线程同时访问时候进行同步

在计算key、condition或者unless的值得时候,可以使用到以下的特有的SpEL表达式

表达式

解释

#result

表示方法的返回结果

#root.method

当前方法

#root.target

目标对象

#root.caches

被影响到的缓存列表

#root.methodName

方法名称简称

#root.targetClass

目标类

#root.args[x]

方法的第x个参数

#参数名称

任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可以在root.args[x]x代表参数索引

@CachePut

无论怎样都会执行方法,并将方法返回值放入缓存

@CacheEvict

该注解在执行完方法后会触发一次缓存evict操作,参数除了@Cacheable里的外,还有个特殊的allEntries, 表示将清空缓存中所有的值。(尽量不要使用allEntries)

查看源码:org.springframework.cache.interceptor.CacheAspectSupport

private void performCacheEvict(
      CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {

   Object key = null;
   for (Cache cache : context.getCaches()) {
          if (operation.isCacheWide()) { //验证是否为删除缓存中的全部值
         logInvalidating(context, operation, null);
         //doClear中删除数据使用的为keys命令,会阻塞redis
         doClear(cache);
      }
      else {
         if (key == null) {
            key = generateKey(context, result);
         }
         logInvalidating(context, operation, key);
         doEvict(cache, key);
      }
   }
}

快速入门

1、maven引入jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、yam中加入redis的配置

3、初始化RedisConfig

        FastJsonHttpMessageConverter还需要引入fastjson的依赖。

@Configuration
@EnableCaching  //开启redis配置
public class RedisConfig extends CachingConfigurerSupport {

    public RedisConfig() {
        super();
    }

    @Bean
    public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, T> template = new RedisTemplate();
        template.setConnectionFactory(connectionFactory);
        //使用fastJson作为默认的序列化方式
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        template.setDefaultSerializer(genericFastJsonRedisSerializer);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setValueSerializer(genericFastJsonRedisSerializer);
        template.setKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(genericFastJsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //初始化一个不加锁验证的RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //声明一个需要加锁验证的CacheWriter,采用当前声明将会验证执行过程中是否加锁及锁等待
//        RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
        RedisCacheConfiguration defaultCacheConfig = this.getRedisCacheConfigurationWithTtl(10);

        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig, this.getRedisCacheConfigurationMap());
        return cacheManager;
    }

    /**
     * 设置redis缓存策略
     *
     * @return
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //添加自己定义的
        redisCacheConfigurationMap.put(RedisKeys.WEPLAY_CONSTELLATION_CONFIG, this.getRedisCacheConfigurationWithTtl(1800L));
        return redisCacheConfigurationMap;
    }

    /**
     * 设置redis缓存配置
     *
     * @param seconds 单位:秒
     * @return
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Long seconds) {
        //设置CacheManager的值序列化方式为json序列化
        RedisSerializer<Object> jsonSerializer = new GenericFastJsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);

        //设置过期时间
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(pair).entryTtl(Duration.ofSeconds(seconds));
        return redisCacheConfiguration;
    }

    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1、需要先定义一个converter 转换器
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteEnumUsingToString, SerializerFeature.WriteNullListAsEmpty);
        // 中文乱码解决方案
        List<MediaType> mediaTypes = new ArrayList<>();
        //设定json格式且编码为UTF-8
        mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(mediaTypes);
        // 3、在convert 中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4、将convert 添加到converters当中
        HttpMessageConverter<?> converter = fastConverter;
        return new HttpMessageConverters(converter);
    }

    @Override
    public KeyGenerator keyGenerator() {
        KeyGenerator keyGenerator = new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName());
                sb.append(method.getName());
                for (Object obj : objects) {
                    sb.append(Optional.ofNullable(obj).map(p -> p.toString()).orElse("-"));
                }
                return sb.toString();
            }
        };
        return keyGenerator;
    }

    @Override
    public CacheResolver cacheResolver() {
        return super.cacheResolver();
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return super.errorHandler();
    }

    /**
     * 生成key
     *
     * @param args
     * @return
     */
    public static String genKey(Object... args) {
        return Optional.ofNullable(args).map(ag -> {
            StringBuilder sb = new StringBuilder();
            int len = ag.length;
            for (int i = 0; i < len; ++i) {
                sb.append(Optional.ofNullable(ag[i]).map(Object::toString).map(s -> s.concat("-")).orElse("[]"));
            }
            return sb.toString();
        }).orElse("[]");
    }

}

过期时间,序列化方式由此类决定 RedisCacheConfiguration,可以覆盖此类达到自定义配置。默认配置为RedisCacheConfiguration.defaultCacheConfig() ,它配置为永不过期,key 为 String 序列化,并加上了一个前缀做为命名空间,value 为 Jdk 序列化,所以你要存储的类必须要实现 java.io.Serializable

存储的 key 值的生成由 KeyGenerator 决定,可以在各缓存注解上进行配置,默认使用的是 SimpleKeyGenerator 其存储的 key 方式为 SimpleKey [参数名1,参数名2],如果在同一个命名空间下,有两个同参数名的方法就公出现冲突导致反序列化失败。

缓存穿透问题:

使用@Cacheable在并发访问时,会多次访问数据库。在spring4.3版本后增加了一个sync参数。是当缓存失效后,为了避免多个请求打到数据库,系统做了一个并发控制优化,同时只有一个线程会去数据库取数据其它线程会被阻塞。

源码

从 @EnableCaching 开始,可以看到导入了一个选择导入配置的配置类,默认使用 PROXY 模式

包路径:org.springframework.cache.annotation.CachingConfigurationSelector

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching>

导入配置

@Override
public String[] selectImports(AdviceMode adviceMode) {
   switch (adviceMode) {
      case PROXY:
         return getProxyImports();
      case ASPECTJ:
         return getAspectJImports();
      default:
         return null;
   }
}

进入getAspectJImports()方法查看代码

private String[] getProxyImports() {
   List<String> result = new ArrayList<>(3);
   result.add(AutoProxyRegistrar.class.getName());
   result.add(ProxyCachingConfiguration.class.getName());
   if (jsr107Present && jcacheImplPresent) {
      result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
   }
   return StringUtils.toStringArray(result);
}

其中ProxyCachingConfiguration是核心

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

   //BeanFactoryCacheOperationSourceAdvisor 是 CacheOperationSource 的一个增强器 
   @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
      BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
      advisor.setCacheOperationSource(cacheOperationSource());
      advisor.setAdvice(cacheInterceptor());
      if (this.enableCaching != null) {
         advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
      }
      return advisor;
   }

   //CacheOperationSource 主要提供查找方法上缓存注解的方法 findCacheOperations
   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public CacheOperationSource cacheOperationSource() {
      return new AnnotationCacheOperationSource();
   }

   //CacheInterceptor 它是一个 MethodInterceptor 在调用缓存方法时,会执行它的 invoke 方法
   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public CacheInterceptor cacheInterceptor() {
      CacheInterceptor interceptor = new CacheInterceptor();
      interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
      interceptor.setCacheOperationSource(cacheOperationSource());
      return interceptor;
   }

}

下面来看一下 CacheInterceptor 的 invoke 方法

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
   @Override
   @Nullable
   public Object invoke(final MethodInvocation invocation) throws Throwable {
      Method method = invocation.getMethod();
      CacheOperationInvoker aopAllianceInvoker = () -> {
         try {
            return invocation.proceed();
         }
         catch (Throwable ex) {
            throw new CacheOperationInvoker.ThrowableWrapper(ex);
         }
      };
      try {
          //aopAllianceInvoker 是一个函数式接口,会执行你的真实方法
         return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
      }
      catch (CacheOperationInvoker.ThrowableWrapper th) {
         throw th.getOriginal();
      }
   }

}

进入 execute 方法,可以看到这一层只是获取到所有的缓存操作集合,@CacheConfig,@Cacheable,@CachePut,@CacheEvict,@Caching 然后把其配置和当前执行上下文进行绑定成了 CacheOperationContexts

@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
   // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
   if (this.initialized) {
      Class<?> targetClass = getTargetClass(target);
      CacheOperationSource cacheOperationSource = getCacheOperationSource();
      if (cacheOperationSource != null) {
          //获取到所有的缓存操作集合,@CacheConfig,@Cacheable,@CachePut,@CacheEvict,@Caching 然后把其配置和当前执行上下文进行绑定成了 CacheOperationContexts  
         Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
         if (!CollectionUtils.isEmpty(operations)) {
            return execute(invoker, method,
                  new CacheOperationContexts(operations, method, args, target, targetClass));
         }
      }
   }
   return invoker.invoke();
}

再进入 execute 方法,这里就是整合的核心处理逻辑了

@Nullable
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
        if (contexts.isSynchronized()) {
            //如果使用的有sync参数将会单独进行处理
        }

        // Process any early evictions 
        //先做缓存清理工作
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                CacheOperationExpressionEvaluator.NO_RESULT);

        // 查询缓存中内容 
        // Check if we have a cached item matching the conditions
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        //如果缓存没有命中,收集 put 请求,后面会统一把需要放入缓存中的统一应用
        // Collect puts from any @Cacheable miss, if no cached item is found 
        List<CachePutRequest> cachePutRequests = new LinkedList<>();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;

        // 缓存有命中并且不是 @CachePut 的处理
        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            cacheValue = cacheHit.get();
            returnValue = wrapCacheValue(method, cacheValue);
        }
        else {
            //缓存没有命中,执行真实方法
            // Invoke the method if we don't have a cache hit 
            returnValue = invokeOperation(invoker);
            cacheValue = unwrapReturnValue(returnValue);
        }

        // Collect any explicit @CachePuts
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        //把前面收集到的所有 putRequest 数据放入缓存
        // Process any collected put requests, either from @CachePut or a @Cacheable miss 
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // Process any late evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }

无sync逻辑

将数据放入缓存cachePutRequest.apply(cacheValue) 的方法代码

public void apply(@Nullable Object result) {
   if (this.context.canPutToCache(result)) {
      for (Cache cache : this.context.getCaches()) {
         doPut(cache, this.key, result);
      }
   }
}

doPut方法内部调用到了Cache.put()方法,其中Cache类为接口类,其最终实现在RedisCache中

@Override
public void put(Object key, @Nullable Object value) {
   //value验证 
   Object cacheValue = preProcessCacheValue(value);

   if (!isAllowNullValues() && cacheValue == null) {

      throw new IllegalArgumentException(String.format(
            "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
            name));
   }
   //执行添加缓存
   cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}

接着看下CacheWriter的put方法, 调用的是org.springframework.data.redis.cache.DefaultRedisCacheWriter方法内部的put方法

@Override
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {

   Assert.notNull(name, "Name must not be null!");
   Assert.notNull(key, "Key must not be null!");
   Assert.notNull(value, "Value must not be null!");
    //函数式接口,设置缓存
   execute(name, connection -> {
       //如果设置的有超时时间则设置过期时间
      if (shouldExpireWithin(ttl)) {
         connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
      } else {
          //无过期时间则直接设置
         connection.set(key, value);
      }

      return "OK";
   });
}

综上来看,直接使用@Cacheable存在缓存击穿的问题,在查询设置缓存的时候,全程未加锁。所以会多次执行数据库查询和设置缓存。为解决上述问题spring-context4.3版本引入sync参数,当设置sync参数时会执行excute中的sync部分代码块。

sync逻辑

if (contexts.isSynchronized()) {
   CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
    //是否满足条件
   if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
       //将KEY有spel表达式解析出值
      Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
      Cache cache = context.getCaches().iterator().next();
      try {
         //返回数据  函数式接口格式化value信息
         return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
      }
      catch (Cache.ValueRetrievalException ex) {
         // The invoker wraps any Throwable in a ThrowableWrapper instance so we
         // can just make sure that one bubbles up the stack.
         throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
      }
   }
   else {
      // No caching required, only call the underlying method
      return invokeOperation(invoker);
   }
}

从上面的代码来看并没有实现同步相关的代码,实际上加锁信息在cache.get()的实现中

@Override
@SuppressWarnings("unchecked")
public synchronized <T> T get(Object key, Callable<T> valueLoader) {

   ValueWrapper result = get(key);

   if (result != null) {
      return (T) result.get();
   }

   T value = valueFromLoader(key, valueLoader);
   put(key, value);
   return value;
}

从上面的代码可以看到这个get方法使用了synchronized关键字,使其变为线程阻塞的获取,所以增加了sync参数后只会获取一次

虽然增加了sync参数,但是由于使用的synchronized关键字只是当前节点的机器变为阻塞获取,如果是分布式系统仍然会存在多次请求进入查询的情况,但是相对于缓存击穿的场景还是优化了很多

@CacheEvit逻辑

在CacheAspectSupport.execute执行完成后将验证是否为@CacheEvit注解

// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

其内部代码为

private void processCacheEvicts(
      Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {

   for (CacheOperationContext context : contexts) {
      CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
      if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
          //执行删除缓存
         performCacheEvict(context, operation, result);
      }
   }
}

@CacheEvit注解中allEntries默认值为false,执行器根据allEntries的值设置执行不同的处理方法

private void performCacheEvict(
      CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {

   Object key = null;
   for (Cache cache : context.getCaches()) {
       //验证allEntries参数是否为true
      if (operation.isCacheWide()) {
         logInvalidating(context, operation, null);
         //执行的为keys命令检查符合条件的数据,最后再执行del命令
         doClear(cache);
      }
      else {
         if (key == null) {
            key = generateKey(context, result);
         }
         logInvalidating(context, operation, key);
         //最终执行为redis的del命令
         doEvict(cache, key);
      }
   }
}

其中doClear方法主要调用的为DefaultRedisCacheWriter.clean方法

@Override
public void clean(String name, byte[] pattern) {

   Assert.notNull(name, "Name must not be null!");
   Assert.notNull(pattern, "Pattern must not be null!");

   execute(name, connection -> {

      boolean wasLocked = false;

      try {
        //CacheManager声明是否为lock,如果为lock则返回true
         if (isLockingCacheWriter()) {
             //加锁,key的名称为cacheName+"~lock",设置方式的setNx
            doLock(name, connection);
            wasLocked = true;
         }
        //使用keys命令找出符合条件的数据
         byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
               .toArray(new byte[0][]);

         if (keys.length > 0) {
            //执行删除  
            connection.del(keys);
         }
      } finally {
          //解锁
         if (wasLocked && isLockingCacheWriter()) {
            doUnlock(name, connection);
         }
      }

      return "OK";
   });
}

看了解锁的代码可以发现在使用allEntries参数时还是存在很大的问题,在dolock方法中加锁是没有超时时间的,如果在加完锁之后服务宕机,重启后重新进入这个逻辑将会无限sleep获取锁。对于allEntries还是尽量少使用。

execute中执行锁状态验证的代码

private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {

   if (!isLockingCacheWriter()) {
      return;
   }

   try {

      while (doCheckLock(name, connection)) {
         Thread.sleep(sleepTime.toMillis());
      }
   } catch (InterruptedException ex) {

      // Re-interrupt current thread, to allow other participants to react.
      Thread.currentThread().interrupt();

      throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name),
            ex);
   }
}

CacheAspectSupport 

看完了执行流程,现在看一下CacheInterceptor 的超类 CacheAspectSupport ,因为可以不设置 cacheManager 就可以使用,查看默认的 cacheManager是在哪设置的

public abstract class CacheAspectSupport extends AbstractCacheInvoker
        implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
    // .... 
}

BeanFactoryAware 用来获取 BeanFactory

InitializingBean 用来管理 Bean 的生命周期,可以在 afterPropertiesSet后添加逻辑

SmartInitializingSingleton 实现该接口后,当所有单例 bean 都初始化完成以后, 容器会回调该接口的方法 afterSingletonsInstantiated

在 afterSingletonsInstantiated 中,果然进行了 cacheManager 的设置,从 IOC 容器中拿了一个 cacheManger

setCacheManager(this.beanFactory.getBean(CacheManager.class));

那这个 CacheManager 是谁呢 ,可以从RedisCacheConfiguration类知道答案 ,在这里面配置了一个 RedisCacheManager

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {} 
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
                                      ResourceLoader resourceLoader) {
    RedisCacheManagerBuilder builder = RedisCacheManager
        .builder(redisConnectionFactory)
        .cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
        builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
    }
    return this.customizerInvoker.customize(builder.build());
}

从 determineConfiguration() 方法中可以知道 cacheManager 的默认配置

最后看一下,它的切点是如何定义的,即何时会调用 CacheInterceptor 的 invoke 方法。

切点的配置是在 BeanFactoryCacheOperationSourceAdvisor 类中,返回一个这样的切点 CacheOperationSourcePointcut ,覆写 MethodMatcher 中的 matchs ,如果方法上存在注解 ,则认为可以切入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值