Я знаю, что этот вопрос был давно, но недавно я снова провел некоторое исследование по этой теме, поэтому я хотел бы поделиться, как этот «полухешированный» ключ генерируется путем прохождения части исходного кода Spring здесь.
Прежде всего, Spring использует АОП для разрешения аннотаций типа @Cacheable, @CacheEvict or @CachePut
и т. Д. Класс совета - это CacheInterceptor
из зависимости контекста Spring, которая является подклассом CacheAspectSupport
(также из контекста Spring). Для простоты объяснения я бы использовал @Cacheable
в качестве примера, чтобы пройти здесь по части исходного кода.
Когда вызывается метод, помеченный как @Cacheable
, АОП направит его к этому методу protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver)
из класса CacheAspectSupport
, в котором он попытается разрешить эту @Cacheable
аннотацию. В свою очередь, это приводит к вызову этого метода public Cache getCache(String name)
в реализации CacheManager. Для этого объяснения реализация CacheManage будет RedisCacheManager
(из зависимости Spring-data-redis).
Если кеш не был поражен, он продолжит создание кеша. Ниже приведены ключевые методы из RedisCacheManager
:
protected Cache getMissingCache(String name) {
return this.dynamic ? createCache(name) : null;
}
@SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
По сути, он создаст экземпляр объекта RedisCache
. Для этого требуются 4 параметра, а именно cacheName, prefix (это ключевой параметр для ответа на этот вопрос), redisOperation (он же настроенный redisTemplate), срок действия (по умолчанию 0) и cacheNullValues (по умолчанию false). Конструктор ниже показывает более подробную информацию о RedisCache.
/**
* Constructs a new {@link RedisCache} instance.
*
* @param name cache name
* @param prefix must not be {@literal null} or empty.
* @param redisOperations
* @param expiration
* @param allowNullValues
* @since 1.8
*/
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
long expiration, boolean allowNullValues) {
super(allowNullValues);
Assert.hasText(name, "CacheName must not be null or empty!");
RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
: (RedisSerializer<?>) new JdkSerializationRedisSerializer();
this.cacheMetadata = new RedisCacheMetadata(name, prefix);
this.cacheMetadata.setDefaultExpiration(expiration);
this.redisOperations = redisOperations;
this.cacheValueAccessor = new CacheValueAccessor(serializer);
if (allowNullValues) {
if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
|| redisOperations.getValueSerializer() instanceof GenericToStringSerializer
|| redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
|| redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
throw new IllegalArgumentException(String.format(
"Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
+ "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
+ "Please use a different RedisSerializer or disable null value support.",
ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
}
}
}
Так в чем же польза от prefix
в этом RedisCache? -> Как показано в конструкторе about, он используется в этом операторе this.cacheMetadata = new RedisCacheMetadata(name, prefix);
, а конструктор RedisCacheMetadata
ниже показывает более подробную информацию:
/**
* @param cacheName must not be {@literal null} or empty.
* @param keyPrefix can be {@literal null}.
*/
public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {
Assert.hasText(cacheName, "CacheName must not be null or empty!");
this.cacheName = cacheName;
this.keyPrefix = keyPrefix;
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// name of the set holding the keys
this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
}
На данный момент мы знаем, что для некоторого параметра префикса было установлено значение RedisCacheMetadata
, но как именно этот префикс используется для формирования ключа в Redis (например, \ xac \ xed \ x00 \ x05t \ x00 \ tvc: 501381, как вы упомянули) ?
По сути, CacheInterceptor
впоследствии перейдет к вызову метода private RedisCacheKey getRedisCacheKey(Object key)
из вышеупомянутого объекта RedisCache
, который возвращает экземпляр RedisCacheKey
, используя префикс из RedisCacheMetadata
и keySerializer из RedisOperation
.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
Достигнув этой точки, "предварительная" рекомендация CacheInterceptor
завершается, и он переходит к выполнению фактического метода, аннотированного @Cacheable
. И после завершения выполнения фактического метода он выполнит «отправку» совета CacheInterceptor
, который, по сути, отправит результат в RedisCache. Ниже приведен метод помещения результата в кеш Redis:
public void put(final Object key, final Object value) {
put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
.expireAfter(cacheMetadata.getDefaultExpiration()));
}
/**
* Add the element by adding {@link RedisCacheElement#get()} at {@link RedisCacheElement#getKeyBytes()}. If the cache
* previously contained a mapping for this {@link RedisCacheElement#getKeyBytes()}, the old value is replaced by
* {@link RedisCacheElement#get()}.
*
* @param element must not be {@literal null}.
* @since 1.5
*/
public void put(RedisCacheElement element) {
Assert.notNull(element, "Element must not be null!");
redisOperations
.execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}
Внутри объекта RedisCachePutCallback
его метод обратного вызова doInRedis()
фактически вызывает метод для формирования фактического ключа в redis, а имя метода - getKeyBytes()
из RedisCacheKey
экземпляра. Ниже показаны подробности этого метода:
/**
* Get the {@link Byte} representation of the given key element using prefix if available.
*/
public byte[] getKeyBytes() {
byte[] rawKey = serializeKeyElement();
if (!hasPrefix()) {
return rawKey;
}
byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);
return prefixedKey;
}
Как мы видим в методе getKeyBytes
, он использует как необработанный ключ (vc: 501381 в вашем случае), так и префиксный ключ (\ xac \ xed \ x00 \ x05t \ x00 \ t в вашем случае).
person
imarchuang
schedule
27.10.2017