Как реализовать кеш Guava для хранения и получения разных типов объектов? - PullRequest
0 голосов
/ 07 ноября 2018

Прямо сейчас мой кеш выглядит следующим образом:

public class TestCache {

    private LoadingCache<String, List<ObjectABC>> cache;

    TestCache() {
        cache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).maximumSize(25)
                .build(new CacheLoader<String, List<ObjectABC>>(
                ) {
                    @Override
                    public List<ObjectABC> load(String key) throws Exception {
                        // TODO Auto-generated method stub
                        return addCache(key);
                    }

                });
    }

    private List<ObjectABC> addCache(String key) {
    final JoiObjectMapper mapper = new JoiObjectMapper();

        final Collection<File> allConfigFiles = FileUtils.listFiles(new File(key), null, true);
        final List<ObjectABC> configsList = new ArrayList<>();

        allConfigFiles.forEach(configFile -> {
            try {
                     configsList.add(mapper.readValue(configFile, new TypeReference<ObjectABC>() {
                      }));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        return configsList;
    }

    public List<ObjectABC> getEntry(String key) {
         try {
            return cache.get(key);
        } catch (ExecutionException e) {
            throw new NonRetriableException(String.format(
                    "Exception occured while trying to get data from cache for the key : {} Exception: {}",
                    key.toString(), e));
        }
    }
}

В приведенном выше коде, когда я передаю String key (который является путем к локальной папке), он берет все файлы, находящиеся в этом месте, и сопоставляет их с ObjectABC, используя ObjectMapper.

Теперь моя проблема в том, что я хочу вместо этого иметь общий кэш загрузки, такой как
LoadingCache<String, List<Object>>.

И я хочу отобразить файлы в разных папках на разные объекты, например, файлы карт в / root / Desktop / folder1 в List<ObjectABC> и файлы карт в / root / Desktop / folder2 в List<ObjectDEF> и могут хранить и извлекать эту информацию из кэш.

Как передать в кеш информацию о том, какой объект использовать для отображения?

1 Ответ

0 голосов
/ 07 ноября 2018

Вы можете создать собственный класс, обертывающий LoadingCache<Key<?>, Object> следующим образом:

class HeterogeneousCache {

    private final LoadingCache<Key<?>, Object> cache;

    public <T> T get(Key<T> key) throws ExecutionException {
        return key.getType().cast(cache.get(key));
    }
}

@Value // provides constructor, getters, equals, hashCode
class Key<T> {

    private final String identifier;
    private final Class<T> type;
}

(я использовал для простоты аннотацию Lombok @ Value )

Конечно, это всего лишь заглушка, и вам может понадобиться адаптировать ее к вашим потребностям. Основная проблема может заключаться в том, что вы не можете получить Class<List<ObjectABC>> - вы можете получить только Class<List>. Самый простой выход из этого - обернуть List<ObjectABC> в некоторый пользовательский тип. Более сложный способ (не рекомендуется) - использовать TypeToken.

от Guava.

Атрибуция : Этот ответ основан на посте Фрэнка Аппеля , озаглавленном Как сопоставить типы значений с использованием обобщенных данных Java , который сам основан на Joshua Bloch типобезопасных гетерогенных контейнеров от Effective Java .


Редактировать : полное решение

Поскольку ОП хочет получить List<T> в качестве результата, а поскольку ему нужны экземпляры TypeReference<T>, я заменил Class<T> на TypeReference<T> в Key<T>:

@Value // provides constructor, getters, equals, hashCode
class Key<T> {
    private final String identifier;
    private final TypeReference<T> typeReference;
}

Вот как CustomHeterogeneousCache выглядит сейчас:

class CustomHeterogeneousCache {

    private final LoadingCache<Key<?>, List<?>> cache = CacheBuilder.newBuilder()
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .maximumSize(25)
            .build(CacheLoader.from(this::computeEntry));

    @SuppressWarnings("unchecked")
    public <T> List<T> getEntry(Key<T> key) {
        return (List<T>) cache.getUnchecked(key);
    }

    private <T> List<T> computeEntry(Key<T> key) {
        final JoiObjectMapper mapper = new JoiObjectMapper();
        final Collection<File> allConfigFiles = FileUtils.listFiles(new File(key.getIdentifier()), null, true);
        return allConfigFiles.stream()
                .map(configFile -> {
                    try {
                        return mapper.readValue(configFile, key.getTypeReference());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                })
                .collect(Collectors.toList());
    }
}

Поскольку реализации TypeReference не имеют семантики значений, пользователь должен убедиться, что каждый Key создается один раз, а затем только на него ссылаются, например ::

class Keys {
    public static final Key<ObjectABC> ABC = new Key<>("/root/Desktop/folder1", new TypeReference<ObjectABC>() {
    });
    public static final Key<ObjectDEF> DEF = new Key<>("/root/Desktop/folder2", new TypeReference<ObjectDEF>() {
    });
}
...