You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java无框架场景下,如何合理缓存Repository的部分方法?

如何为Java Repository的部分方法实现缓存(无框架场景)

问题背景

我正在开发一个不依赖Spring或其他框架的Java项目,项目中的Repository类调用成本较高,希望对部分方法的返回结果进行缓存。

最初定义了如下接口与实现类:

原接口定义

public interface ThingInterface {
    Map<Long, String> findById(long id);
    String findNameById(long id);
    String findConfigBySomeParameter(String param);
    Long findAnotherThingByAntoherParam(String param);
}

原Repository实现

public class ThingRepository implements ThingInterface{
    @Override
    public Map<Long, String> findById(long id) {
        // 从数据库或其他数据源获取数据
    }

    @Override
    public String findNameById(long id) {
        // 实现逻辑
    }

    @Override
    public String findConfigBySomeParameter(String param) {
        // 实现逻辑
    }

    @Override
    public Long findAnotherThingByAntoherParam(String param) {
        // 实现逻辑
    }
}

尝试用装饰器模式实现CachableThingRepository时遇到问题:非缓存方法也被包含在实现类中,外部调用者会误以为所有方法都具备缓存能力。我已经想到一种方案——移除接口,让CachableThingRepository仅包含需要缓存的方法,但希望了解其他可行思路。


可行解决方案

方案1:拆分接口,区分缓存与非缓存方法

将原有的单一接口拆分为两个独立接口,分别对应需要缓存和不需要缓存的方法,让实现类同时实现两个接口,缓存装饰器仅实现缓存接口。

// 缓存方法接口
public interface CachableThingInterface {
    Map<Long, String> findById(long id);
    String findConfigBySomeParameter(String param);
}

// 非缓存方法接口
public interface NonCachableThingInterface {
    String findNameById(long id);
    Long findAnotherThingByAntoherParam(String param);
}

// 原Repository同时实现两个接口
public class ThingRepository implements CachableThingInterface, NonCachableThingInterface {
    // 保持原有所有方法的实现逻辑
    @Override
    public Map<Long, String> findById(long id) { /* ... */ }
    @Override
    public String findNameById(long id) { /* ... */ }
    @Override
    public String findConfigBySomeParameter(String param) { /* ... */ }
    @Override
    public Long findAnotherThingByAntoherParam(String param) { /* ... */ }
}

// 缓存装饰器仅实现缓存接口
public class CachableThingRepository implements CachableThingInterface {
    private final CachableThingInterface delegate;
    private final Map<Long, Map<Long, String>> idCache = new ConcurrentHashMap<>();
    private final Map<String, String> configCache = new ConcurrentHashMap<>();

    public CachableThingRepository(CachableThingInterface delegate) {
        this.delegate = delegate;
    }

    @Override
    public Map<Long, String> findById(long id) {
        return idCache.computeIfAbsent(id, delegate::findById);
    }

    @Override
    public String findConfigBySomeParameter(String param) {
        return configCache.computeIfAbsent(param, delegate::findConfigBySomeParameter);
    }
}

这种设计下,调用者可以根据需求选择依赖的接口:需要缓存方法时使用CachableThingRepository,需要非缓存方法时直接使用ThingRepository,完全避免误解。

方案2:组合模式替代装饰器,明确暴露缓存方法

创建独立的缓存类,内部持有原Repository实例,仅对外暴露需要缓存的方法;如果必须提供非缓存方法,通过明确的命名后缀(如WithoutCache)区分,避免混淆。

public class ThingCache {
    private final ThingRepository repository;
    private final Map<Long, Map<Long, String>> idCache = new ConcurrentHashMap<>();
    private final Map<String, String> configCache = new ConcurrentHashMap<>();

    public ThingCache(ThingRepository repository) {
        this.repository = repository;
    }

    // 仅暴露带缓存的方法
    public Map<Long, String> findById(long id) {
        return idCache.computeIfAbsent(id, repository::findById);
    }

    public String findConfigBySomeParameter(String param) {
        return configCache.computeIfAbsent(param, repository::findConfigBySomeParameter);
    }

    // 若需提供非缓存方法,用明确命名区分
    public String findNameByIdWithoutCache(long id) {
        return repository.findNameById(id);
    }
}

这种方式逻辑简单直接,调用者能清晰识别哪些方法是缓存过的,不会产生歧义。

方案3:自定义注解+动态代理实现

通过自定义注解标记需要缓存的方法,结合JDK动态代理实现缓存逻辑,无需修改原接口结构,同时明确告知调用者哪些方法有缓存。

步骤1:定义缓存注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {
}

步骤2:标记接口中的缓存方法

public interface ThingInterface {
    @Cacheable
    Map<Long, String> findById(long id);
    String findNameById(long id);
    @Cacheable
    String findConfigBySomeParameter(String param);
    Long findAnotherThingByAntoherParam(String param);
}

步骤3:实现动态代理处理器

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CacheInvocationHandler implements InvocationHandler {
    private final Object target;
    private final Map<Method, Map<Object, Object>> methodCache = new ConcurrentHashMap<>();

    public CacheInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.isAnnotationPresent(Cacheable.class)) {
            // 用方法参数的哈希值作为缓存键,可根据实际需求优化键的生成逻辑
            Object cacheKey = Arrays.hashCode(args);
            return methodCache.computeIfAbsent(method, m -> new ConcurrentHashMap<>())
                       .computeIfAbsent(cacheKey, k -> method.invoke(target, args));
        }
        // 无缓存注解的方法直接调用原实现
        return method.invoke(target, args);
    }

    // 静态工厂方法创建代理实例
    public static <T> T createCachedProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new CacheInvocationHandler(target)
        );
    }
}

步骤4:使用代理类

ThingInterface originalRepo = new ThingRepository();
ThingInterface cachedRepo = CacheInvocationHandler.createCachedProxy(originalRepo);

// 调用带@Cacheable注解的方法会走缓存逻辑
cachedRepo.findById(1L);
// 无注解的方法直接调用原实现
cachedRepo.findNameById(1L);

这种方案兼顾了灵活性和可读性,既不需要拆分接口,又能明确标记缓存方法,调用者通过注解就能清楚知道哪些方法有缓存。


内容的提问来源于stack exchange,提问作者Andrei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:28:15