Я пытаюсь использовать Spring Cacheable, но получаю исключение приведения класса
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CacheableTest.class, loader = AnnotationConfigContextLoader.class)
@Configuration
@EnableCaching
public class CacheableTest {
public static final String CACHE = "cache";
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(CACHE);
}
@Autowired
DataService service;
@Test
public void cacheTest() {
final String name = "name";
Data a = service.getData(name);
Data b = service.getData(new String(name));
Assert.assertSame(a, b);
String c = service.getValue(service.getData(name));
String d = service.getValue(service.getData(new String(name)));
Assert.assertSame(c, d);
String e = service.getValue(name);
String f = service.getValue(new String(name));
Assert.assertSame(e, f);
}
public static class Data {
private String value;
public Data(String value) {
this.value = value;
}
}
@Service
public static class DataService {
@Resource
private DataService self;
@Cacheable(CACHE)
public Data getData(String name) {
return new Data(name);
}
@Cacheable(CACHE)
public String getValue(Data data) {
return data.value;
}
@Cacheable(CACHE)
public String getValue(String name) {
return self.getData(name).value;
}
}
}
Исключением является CacheableTest$Data cannot be cast to java.lang.String, который происходит в строке String e. Мы знаем почему?




Вы объявили 2 метода с одинаковыми параметрами, но с другим возвращаемым типом .:
public Data getData(String name)
public String getValue(String name)
И вы пометили их обоих как @Cacheable с тем же именем кеша CACHE. В результате вы используете один кеш для хранения результатов обоих методов. Оба метода имеют точно такие же параметры (String), поэтому ключи кеша, рассчитанные Spring для Data и Value, будут конфликтовать.
Первый вызываемый метод вернет результат, который spring поместит в кеш, а второй попытается получить тот же результат из кеша (потому что имя кеша и параметр метода совпадают) и попытается преобразовать его в другой тип - следовательно, , исключение приведения класса.
Это почти то же самое, как если бы вы сделали это:
Map<String, Object> cache = new HashMap<>();
cache.put("key", "value1");
int i = (Integer)cache.get("key")
Я думаю, что решение - просто использовать отдельные кеши для разных типов. Например. вы можете назвать один кэш "dataCache", другой - "valueCache". Я не могу придумать какой-либо веской причины хранить оба типа объектов в одном кеше. По крайней мере, я исправил это так, и у меня это сработало. Вы также можете создать собственный генератор ключей, но этого не сделали.
Честно говоря, мой ответ выше - это всего лишь лучшее предположение о том, что происходит. Я столкнулся с той же проблемой, поэтому нашел этот вопрос. Мне не удалось найти подробную весеннюю документацию о том, как @Cacheable работает по этой конкретной теме. Если кто-то может указать мне на это, я буду рад дать ссылку на него в своем ответе и при необходимости отредактировать его.
Ах хорошо. Таким образом,
@Cacheableпо умолчанию проверяет только входные параметры, чтобы найти кэшированный результат. Спасибо, что ответили на вопрос "почему". Вы знаете, как решить эту проблему (например, заставить@Cacheableобъединить тип возвращаемого значения + имя метода + входные параметры в качестве уникального ключа для поиска кеша)?