• 使用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);
        }

    效果:

  • 相关阅读:
    spark定制之五:使用说明
    ElasticSearch-5.0安装head插件
    安卓真机或者模拟器运行安装应用时提示 Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]解决办法
    Elasticsearch介绍和安装与使用
    AndroidFine Error:Annotation processors must be explicitly declared now.
    Retrofit全攻略——进阶篇
    WCF基础教程之开篇:创建、测试和调用WCF
    SoapUI简介和入门实例解析
    postman的使用方法详解!最全面的教程
    Postman使用详解
  • 原文地址:https://www.cnblogs.com/DajiangDev/p/3770894.html
Copyright © 2020-2023  润新知