【原】通过抽象泛型模板优雅的设计可扩展代码
背景
更加理解熟悉抽象类在我们实际开发过程中的具体使用场景,以及使用抽象类能给我们开发过程中带来哪些好处
设计流程:
大致流程是从Service层获取数据,先经过Redis,如果Redis没有数据再去查询db,最后把数据塞回到Redis,看似很简单的一个步骤,实际可以写出不同风格的方式。一般来说首先想到的无非是下面这样(面向过程的伪代码)
Object val = redisutils.get("key"); if(val == null || val == ""){ Object obj = userdaoMapper.getUserById("key");
BeanUtils.cover(obj,User.class); }else{ return val; }
上面的伪代码一看通俗易懂,但是在实际开发中,特别是多模块开发的场景下大量充斥着如上的代码的话那么维护起来是一件费劲的事情,不仅要编写大量的重复代码,还要对不同的实体类型进行一个转换,那么有什么办法可以避免这个问题呢?第一个办法想到的就是熟悉AOP,但在这里不打算介绍AOP,而是如博客标题所示(通过抽象泛型模板)
实现方式:
首先创建一个缓存抽象模板,这个抽象模板属于基类, 最好定义成泛型,方便后续的扩展
public abstract class CacheWorker<P, R> { private static Log logger = LogFactory.getLog(CacheWorker.class); @Autowired protected RedisUtil redisUtil; /** * get方式获取缓存 * * @param params 查询参数 * @param expireSeconds 缓存过期时间 * @return 结果 * @throws SQLException */ @SuppressWarnings("unchecked") public R get(P params, Class<R> clazz) { // 获取key,由子类实现 String key = getKey(params); Object res = getCache(key, clazz); // 如果缓存中存在,直接返回 if (res != null) { if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("从redis获取数据 (key:{").append(key).append("})"); logger.debug(sb.toString()); } return (R) res; } if (logger.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("从redis获取数据失败(key:{").append(key).append("}), 准备从DB获取."); logger.debug(sb.toString()); } // 否则去DB中取 R dataFromDb = getDataWhenNoCache(params); // 回写cache if (dataFromDb != null) { setCache(getExpireSeconds(), key, dataFromDb); } return dataFromDb; } /** * 获取过期时间 * * @return */ protected abstract int getExpireSeconds(); /** * set操作 设定缓存 * * @param expireSeconds * @param key * @param dataFromDb */ protected void setCache(int expireSeconds, String key, R dataFromDb) { redisUtil.set(key, dataFromDb, expireSeconds); } /** * set操作 从缓存中取值 * * @param key * @return */ protected Object getCache(String key, Class<R> clazz) { // 尝试获取缓存值 return redisUtil.get(key, clazz); } public void del(P params) { // 获取key,由继承者拼接 String key = getKey(params); redisUtil.delete(key); } /** * 当获取不到缓存时,使用该方法去DB或其他途径取数据 * * @param params * @return * @throws SQLException */ protected abstract R getDataWhenNoCache(P params); /** * 获取key * * @param params * @return */ protected abstract String getKey(P params);
创建一个子类并继承上面的抽象基类,这个子类是一个具体的工作组件,在这个子类里扩展自己的方法。
@Component public class GoodsInfoCacheWorker extends CacheWorker<Integer, Goods> { @Autowired private GoodsMapper goodsMapper; @Override protected Goods getDataWhenNoCache(Integer goodsId) { return goodsMapper.selectByPrimaryKey(goodsId); } @Override protected String getKey(Integer goodsId) { String key = MessageFormat.format(CommonConstant.RedisKey.GOODS_INFO_BY_ID, new Object[] { goodsId }); return key; } @Override protected int getExpireSeconds() { return CommonConstant.RedisKeyExpireSeconds.GOODS_STORE_BY_ID; } }
调用方,通过Spring注入后直接调用 抽象摸板类的 get方法,并传入2个参数,分别是key和对应的Class类。
@Autowired private GoodsInfoCacheWorker goodsInfoCacheWorker; public String getGoodsRandomName(Integer goodsId) { Goods goods = goodsInfoCacheWorker.get(goodsId, Goods.class); long now = System.currentTimeMillis(); // 已经开始了活动,则输出抢购链接 if (goods.getStartTime().getTime() < now && now < goods.getEndTime().getTime()) { return goods.getRandomName(); } return StringUtils.EMPTY; }