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
相关产品推荐
相关产品推荐

