首页 > 技术文章 > 面向对象一: 数据加载器完成缓存

yinyunmoyi 2021-01-18 15:10 原文

面向对象一: 数据加载器完成缓存

某微服务,它的作用是导出一系列复杂的组合信息,那么该微服务取到request后,可能需要进行多次与数据库的交互,比如根据用户ID多次取到完整的用户信息,根据商品ID多次取到完整的商品信息,根据时间信息取到多次商品的购买详情,然后整理这些信息并导出。

当某业务需要进行多次与数据库的相同交互,查询到数据后拼装出结果时,如果每次都进行查询非常耗时,可以考虑做一个缓存的系统,把对象放入map中缓存起来,然后再用key将数据取出:

Object cache = cacheMap.get(key);

明确获取缓存的方式

可以设置一个工具类将方法抽取出来:

Object cache = CacheUtils.getCache(clazz, key);

其中clazz是缓存的类型,因为上面提到的用户信息、商品信息、购买详情信息各不相同,这也就无法统一用某种基类或接口统一表示;key是取缓存需要的键值,比如取出用户信息需要用户ID。

但是这样的设计有一个问题,那就是开发者在调用缓存方法时,需要同时知道key和类型,其实key并不是必须的,而且也是难以获知的,更好的方法是先根据request生成一个上下文对象,然后再取缓存:

Context context = new Context(request);
context.getCache(clazz);

这样处理之后,开发者就只需要给出clazz,而不需要提供key,至于用什么key来取缓存,在context中会根据不同的类型进行灵活选择,封装层次进一步提升。

当然也可以把context注入另一个类CacheManager中,然后由CacheManager提供缓存的功能,Context仅仅作为上下文对象:

CacheManager cacheManager = new CacheManager(context);
cacheManager.getCache(clazz);

这样设计严格符合类的单一职责原则。

泛型的引入

比较难的问题在于缓存的对象各不相同,这也就无法统一用某种基类或接口统一表示,初步设计的getCache方法应该如下:

public <T> T getCache(Class clazz)

当然也可以用统一的接口来替换上述设计,该接口的功能只有标记该对象可以被缓存,但是依旧避免不了在代码某处发生类型转换,所以暂时还是采用泛型的做法。

多种类型的数据加载器

可以确定的是,不同类型的缓存对象,查询数据库的方式各不相同,同时利用已知信息context的形式也各不相同,但是每次取缓存的动作是统一的,因此我们建立一个数据加载的接口:

public interface DataLoad {
    <T> T load(Context context);
}

然后用不同类型的数据加载器去完成查询数据库的动作,如加载用户数据:

public <T> T load(Context context) {
    // 查到用户的ID
    String userId = context.getUserId();
    // 根据用户ID取到用户信息
    UserMessage userMessage = userMessageDao.query(userId);
    return userMessage
}

设置缓存

用数据加载器加载完之后,将数据返回,然后就可以将该数据缓存起来,准备下一次取用:

public <T> T getCache(Class clazz) {
    // 类型作为key
    String key = clazz.toString();
    if (cacheMap.get(key) == null) {
        // 根据不同类型取到数据加载器
        DataLoad dataLoad = dataLoads.get(clazz);
        // 加载数据并返回
        T value = dataLoad.load(context);
        // 设置到缓存中
        cacheMap.put(key, value);
        return value;
    }
    return (T) cacheMap.get(key);
}

上面的dataLoads是一个可以根据类型取数据加载器的map,在初始化时填充这个集合的值,可以利用spring自动找到所有实现了DataLoad的类:

// 找到所有实现DataLoad的类
Map<String, DataLoad> nameToDataLoad = SpringContext.getApplicationContext().getBeansOfType(DataLoad.class);
// 填充dataLoads
for (DataLoad dataLoad : nameToDataLoad.values()) {
     dataLoads.put(dataLoad.getClass(), dataLoad);
}

而实现缓存的map就是一个简单的HashMap:

private Map<String, Object> cacheMap = new HashMap<>();

推荐阅读