使用AOP 实现Redis缓存注解,支持SPEL

公司项目对Redis使用比较多,因为之前没有做AOP,所以缓存逻辑和业务逻辑交织在一起,维护比较艰难
所以最近实现了针对于Redis的@Cacheable,把缓存的对象依照类别分别存放到redis的Hash中,对于key也实现了SPEL支持。

1.applicationContext.xml,配置JedisPool

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="50" />
        <property name="maxIdle" value="10" />
        <property name="maxWaitMillis" value="1000" />
        <property name="testOnBorrow" value="true" />
    </bean>

    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig" />
        <constructor-arg index="1" value="127.0.0.1" />
        <constructor-arg index="2" value="6379" />
    </bean>

2.Redis的封装类,使用FastJSON进行JSON和Object的转化,这里只用到了hset,hget,hdel,其他省略了

@Component
    public class RedisCacheBean {
        @Resource
        JedisPool jedisPool;

        /**
         * 把对象放入Hash中
         */
        public void hset(String key,String field,Object o){
            Jedis jedis =jedisPool.getResource();
            jedis.hset(key,field, JsonUtil.toJSONString(o));
            jedisPool.returnResource(jedis);
        }
        /**
         * 从Hash中获取对象
         */
        public String hget(String key,String field){
            Jedis jedis =jedisPool.getResource();
            String text=jedis.hget(key,field);
            jedisPool.returnResource(jedis);
            return text;
        }
        /**
         * 从Hash中获取对象,转换成制定类型
         */
        public <T> T hget(String key,String field,Class<T> clazz){
            String text=hget(key, field);
            T result=JsonUtil.parseObject(text, clazz);
            return result;
        }
        /**
         * 从Hash中删除对象
         */
        public void hdel(String key,String ... field){
            Jedis jedis =jedisPool.getResource();
            Object result=jedis.hdel(key,field);
            jedisPool.returnResource(jedis);
        }
        
    }

3.创建注解,其实大部分数据都是以hash形式存储的(使的key易于管理),所以,注解中定义了fieldKey,用作Hash的field。

/**
     * 缓存注解
     * @author liudajiang
     *
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Cacheable {
        String key();
        String fieldKey() ;
        int expireTime() default 3600;
    }

4.定义切面,定义PointCut 表达式为注解

@Component
    @Aspect
    public class CacheAspect {
        @Resource RedisCacheBean redis;
        
        /**          
        * 定义缓存逻辑                    
        */
        @Around("@annotation(org.myshop.cache.annotation.Cacheable)")
        public Object cache(ProceedingJoinPoint pjp ) {
            Object result=null;
            Boolean cacheEnable=SystemConfig.getInstance().getCacheEnabled();
            //判断是否开启缓存
            if(!cacheEnable){
                try {
                    result= pjp.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                return result;
            }
            
            Method method=getMethod(pjp);
            Cacheable cacheable=method.getAnnotation(org.myshop.cache.annotation.Cacheable.class);
            
            String fieldKey =parseKey(cacheable.fieldKey(),method,pjp.getArgs());
            
            //获取方法的返回类型,让缓存可以返回正确的类型
            Class returnType=((MethodSignature)pjp.getSignature()).getReturnType();
            
            //使用redis 的hash进行存取,易于管理
            result= redis.hget(cacheable.key(), fieldKey,returnType);
            
            if(result==null){
                try {
                    result=pjp.proceed();
                    Assert.notNull(fieldKey);
                    redis.hset(cacheable.key(),fieldKey, result);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
            return result;
        }

        /**          * 定义清除缓存逻辑          */
        @Around(value="@annotation(org.myshop.cache.annotation.CacheEvict)")
        public Object evict(ProceedingJoinPoint pjp ){
            //和cache类似,使用Jedis.hdel()删除缓存即可...
        }
        
        /**
         *  获取被拦截方法对象
         *  
         *  MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
         *    而缓存的注解在实现类的方法上
         *  所以应该使用反射获取当前对象的方法对象
         */
        public Method getMethod(ProceedingJoinPoint pjp){
            //获取参数的类型
            Object [] args=pjp.getArgs();
            Class [] argTypes=new Class[pjp.getArgs().length];
            for(int i=0;i<args.length;i++){
                argTypes[i]=args[i].getClass();
            }
            Method method=null;
            try {
                method=pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(),argTypes);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            }
            return method;
            
        }
        /**
         *    获取缓存的key 
         *    key 定义在注解上,支持SPEL表达式
         * @param pjp
         * @return
         */
        private String parseKey(String key,Method method,Object [] args){
            
            
            //获取被拦截方法参数名列表(使用Spring支持类库)
            LocalVariableTableParameterNameDiscoverer u =   
                new LocalVariableTableParameterNameDiscoverer();  
            String [] paraNameArr=u.getParameterNames(method);
            
            //使用SPEL进行key的解析
            ExpressionParser parser = new SpelExpressionParser(); 
            //SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            //把方法参数放入SPEL上下文中
            for(int i=0;i<paraNameArr.length;i++){
                context.setVariable(paraNameArr[i], args[i]);
            }
            return parser.parseExpression(key).getValue(context,String.class);
        }
    }

5.使用

    @Transactional
    @Cacheable(key="getAdminByName",fieldKey="#name")
    public Admin getByName(String name) {
        return adminDao.getByUsername(name);
    }
    @Transactional
    @CacheEvict(key="getAdminByName",fieldKey="#admin.username")
    public void update(Admin admin){
        adminDao.update(admin);
    }

效果:

你可能感兴趣的:(redis)