1. 程式人生 > >使用AOP 實現Redis快取註解,支援SPEL

使用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);
    }
複製程式碼

效果: