1717
1818import java .lang .reflect .Method ;
1919import java .nio .ByteBuffer ;
20+ import java .util .Arrays ;
21+ import java .util .Collection ;
22+ import java .util .Map ;
23+ import java .util .Map .Entry ;
24+ import java .util .StringJoiner ;
2025import java .util .concurrent .Callable ;
2126
2227import org .springframework .cache .support .AbstractValueAdaptingCache ;
2328import org .springframework .cache .support .NullValue ;
2429import org .springframework .cache .support .SimpleValueWrapper ;
30+ import org .springframework .core .convert .ConversionFailedException ;
2531import org .springframework .core .convert .ConversionService ;
2632import org .springframework .core .convert .TypeDescriptor ;
2733import org .springframework .data .redis .serializer .RedisSerializer ;
3339
3440/**
3541 * {@link org.springframework.cache.Cache} implementation using for Redis as underlying store.
36- * <p />
42+ * <p/>
3743 * Use {@link RedisCacheManager} to create {@link RedisCache} instances.
3844 *
3945 * @author Christoph Strobl
4046 * @author Mark Paluch
41- * @since 2.0
4247 * @see RedisCacheConfiguration
4348 * @see RedisCacheWriter
49+ * @since 2.0
4450 */
4551public class RedisCache extends AbstractValueAdaptingCache {
4652
@@ -281,8 +287,19 @@ protected String createCacheKey(Object key) {
281287protected String convertKey (Object key ) {
282288
283289TypeDescriptor source = TypeDescriptor .forObject (key );
290+
284291if (conversionService .canConvert (source , TypeDescriptor .valueOf (String .class ))) {
285- return conversionService .convert (key , String .class );
292+ try {
293+ return conversionService .convert (key , String .class );
294+ } catch (ConversionFailedException e ) {
295+
296+ // may fail if the given key is a collection
297+ if (isCollectionLikeOrMap (source )) {
298+ return convertCollectionLikeOrMapKey (key , source );
299+ }
300+
301+ throw e ;
302+ }
286303}
287304
288305Method toString = ReflectionUtils .findMethod (key .getClass (), "toString" );
@@ -291,8 +308,39 @@ protected String convertKey(Object key) {
291308return key .toString ();
292309}
293310
294- throw new IllegalStateException (
295- String .format ("Cannot convert %s to String. Register a Converter or override toString()." , source ));
311+ throw new IllegalStateException (String .format (
312+ "Cannot convert cache key %s to String. Please provide a suitable Converter via 'RedisCacheConfiguration.withConversionService(...)' or override '%s.toString()'." ,
313+ source , key != null ? key .getClass ().getSimpleName () : "Object" ));
314+ }
315+
316+ @ Nullable
317+ private String convertCollectionLikeOrMapKey (Object key , TypeDescriptor source ) {
318+
319+ if (source .isMap ()) {
320+
321+ String target = "{" ;
322+ for (Entry <?, ?> entry : ((Map <?, ?>) key ).entrySet ()) {
323+ target += (convertKey (entry .getKey ()) + "=" + convertKey (entry .getValue ()));
324+ }
325+ target += "}" ;
326+ return target ;
327+ } else if (source .isCollection () || source .isArray ()) {
328+
329+ StringJoiner sj = new StringJoiner ("," );
330+
331+ Collection <?> collection = source .isCollection () ? (Collection <?>) key
332+ : Arrays .asList (ObjectUtils .toObjectArray (key ));
333+
334+ for (Object val : collection ) {
335+ sj .add (convertKey (val ));
336+ }
337+ return "[" + sj .toString () + "]" ;
338+ }
339+ return null ;
340+ }
341+
342+ private boolean isCollectionLikeOrMap (TypeDescriptor source ) {
343+ return source .isArray () || source .isCollection () || source .isMap ();
296344}
297345
298346private byte [] createAndConvertCacheKey (Object key ) {
0 commit comments