使用注解+Aop把缓存操作从业务代码分离出来

@Service
public class ContentServiceImpl implements ContentService {

   @Autowired
   private TbContentMapper contentMapper;
   @Autowired
   private JedisClient jedisClient;
   @Value("${INDEX_CONTENT_REDIS_KEY}")
   private String INDEX_CONTENT_REDIS_KEY;
   
   
   @Override
   public List<TbContent> getContentList(long contentCid) {
      //从缓存中取内容
      try {
         String result = jedisClient.hget(INDEX_CONTENT_REDIS_KEY, contentCid + "");
         if (!StringUtils.isBlank(result)) {
            //把字符串转换成list
            List<TbContent> resultList = JsonUtils.jsonToList(result, TbContent.class);
            return resultList;
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      
      //根据内容分类id查询内容列表
   TbContentExample example = new TbContentExample();
      Criteria criteria = example.createCriteria();
      criteria.andCategoryIdEqualTo(contentCid);
      //执行查询
   List<TbContent> list = contentMapper.selectByExample(example);
      
      //向缓存中添加内容
      try {
         //list转换成字符串
         String cacheString = JsonUtils.objectToJson(list);
         jedisClient.hset(INDEX_CONTENT_REDIS_KEY, contentCid + "", cacheString);
      } catch (Exception e) {
         e.printStackTrace();
      }
      
      return list;
   }

}
上述代码是一个获取内容分类的方法,但实际和获取内容分类有关的代码仅仅是

这一段代码而已,前后的代码是从缓存取内容和把内容放入缓存的操作,这两个操作与业务无关,带来很多问题:

1)降低代码易读性;

2)这两个操作都需要加try-catch,以避免影响主业务流程的进行;

3)代码冗余度高,每个需要缓存的都需要写一遍,如果缓存方式变动,所有涉及的地方都需要更改,非常不灵活;

本文使用注解+AOP的方式,把缓存的操作从业务代码里分离出来

实现步骤:

1、创建自定义注解@MyCaChe

@Target({ElementType.METHOD,ElementType.TYPE}) //作用于类和方法
@Retention(RetentionPolicy.RUNTIME) //运行时
public @interface MyCache {
    
    String cacheName(); //缓存名
  String key(); //键值,支持EL表达式
}

2、测试自定义注解

public class TestAnnotation {

    @MyCache(cacheName = "TestCache",key = "#param")
    public String Hello(String param){
        return param;
    }

    @Test
    public void testAnnoMycache() throws NoSuchMethodException {
        Class cls = TestAnnotation.class;
        Method mtd = cls.getMethod("Hello",String.class);
        MyCache anno = mtd.getAnnotation(MyCache.class);
        String cacheName = anno.cacheName();
        String key = anno.key();
        System.out.println("cacheName:"+cacheName+"||key:"+key);
    }
}
测试结果:
 cacheName:TestCache||key:#param

3、修改业务代码

@Service
public class ContentServiceImpl implements ContentService {

   @Autowired
   private TbContentMapper contentMapper;
   @Autowired
   private JedisClient jedisClient;
// @Value("${INDEX_CONTENT_REDIS_KEY}")
// private String INDEX_CONTENT_REDIS_KEY;
   private static final String INDEX_CONTENT_REDIS_KEY = "INDEX_CONTENT_REDIS_KEY"; //修改为常量
   
   @Override
   @MyCache(cacheName = INDEX_CONTENT_REDIS_KEY,key="#contentCid") //加入这一行注解,其中keyEL表达式,实际值为形参contentCid的值
   public List<TbContent> getContentList(Long contentCid) { //由于LocalVariableTableParameterNameDiscoverer无法获取类型为原始类型long.int等形参的形参名,所以使用包装类替换
      //根据内容分类id查询内容列表
      TbContentExample example = new TbContentExample();
      Criteria criteria = example.createCriteria();
      criteria.andCategoryIdEqualTo(contentCid);
      //执行查询
      List<TbContent> list = contentMapper.selectByExample(example);
      return list;
   }
}

4、创建切面类

@Component //记得要扫描
@Aspect
public class MyCacheAspect {
  
    @Autowired
    private JedisClient jedisClient;

    @Around("@annotation(myCache)") //配置切入点,被MyCache注解的方法
    public Object doCache(ProceedingJoinPoint pjp, MyCache myCache) throws Throwable {
        //获取key
        String key = getKey(myCache.key(),pjp);
        String cacheName = myCache.cacheName();
        //从缓存中取内容
        String result = jedisClient.hget(cacheName, key);
        if (!StringUtils.isBlank(result)) {
            //把字符串转换成Object
            Object ret = JsonUtils.jsonToPojo(result, Object.class);
            return ret;
        }

        Object ret  = pjp.proceed(); //执行主业务代码

        //向缓存中添加内容
        //把业务执行返回转换成JSON字符串
        String cacheString = JsonUtils.objectToJson(ret);
        jedisClient.hset(cacheName, key , cacheString);
        return ret;
    }

    /**
     * 解析key
     * @param key 键值,支持EL表达式
     * @param pjp
     * @return
     */
    private String getKey(String key, ProceedingJoinPoint pjp) {
        Method mtd = ((MethodSignature)pjp.getSignature()).getMethod(); //获取方法
        String [] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(mtd); //获取方法的形参名数组
        Object[] args = pjp.getArgs(); //获取形参值数组
        return SpelParser.parseEl(key, paramNames, args); //使用EL解析工具类进行解析
    }
}

5、SpelParser工具类

/**
 *  EL表达式解析工具类
 *  @author mutongli
 *  @date 2018-04-23
 */
public class SpelParser {
    private static ExpressionParser parser = new SpelExpressionParser();

    /**
     * 解析EL表达式
     * @param key 表达式
     * @param paramName 参数名数组
     * @param args 参数值数组
     * @return EL表达式运算结果
     */
    public static String parseEl(String key,String [] paramName,Object[] args){
        Expression exp = parser.parseExpression(key); //key解析为EL表达式
        EvaluationContext context = new StandardEvaluationContext(exp); //创建赋值上下文
        if(args.length <= 0){
            return null;
        }
        //将形参和形参值以配对的方式配置到赋值上下文中
        for (int i=0;i<args.length;i++){
            context.setVariable(paramName[i],args[i]);
        }
        //根据赋值上下文运算EL表达式
        return exp.getValue(context,String.class);
    }
}

运行程序,进行调试发现切面类的getKey方法里的LocalVariableTableParameterNameDiscoverer取不到形参的名字,发现是因为pjp的getSignature取的是接口ContentService,而接口的字节码class文件里找不到形参名,所以对getKey方法进行调整(此处属于个人理解和试验所发现的问题,如果有发现不一样的或者其他正确的结论/方式,希望能够分享一下)

/**
     * 解析key
     * @param key 键值,支持EL表达式
     * @param pjp
     * @return
     */
    private String getKey(String key, ProceedingJoinPoint pjp) throws NoSuchMethodException {
        Class cls = pjp.getTarget().getClass(); //获取目标类信息
        MethodSignature mts = (MethodSignature)pjp.getSignature();
        String mtdname = mts.getName(); //获取方法名
        Method mtd = cls.getMethod(mtdname,mts.getParameterTypes()); //获取目标方法信息
//        String [] paramNames = ((MethodSignature)pjp.getSignature()).getParameterNames();
//        Method mtd = ((MethodSignature) pjp.getSignature()).getMethod(); //获取方法(由于获取的是接口的方法信息,无法获取形参名数组)
        String [] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(mtd); //获取方法的形参名数组
        Object[] args = pjp.getArgs(); //获取形参值数组
        return SpelParser.parseEl(key, paramNames, args); //使用EL解析工具类进行解析
    }

6、调整后重新执行发现能够正常获取形参名数组



在redis里也可以查询到缓存信息


到此就结束了,该文章是本人的第二篇文章,文笔和描述会比较生疏,如有错误的地方,望指出!谢谢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值