aop切面和redis实现自定义缓存注解

来源:互联网 发布:酷狗音乐连不上网络 编辑:程序博客网 时间:2024/06/10 06:16

1.spring-context文件需要打开切面注解

<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:annotation-config />
2.编写切面,如下,

package com.yeepay.g3.core.payplus.test.aspect;import java.lang.reflect.Method;import javax.annotation.Resource;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.stereotype.Component;import org.springframework.util.Assert;import com.yeepay.g3.core.payplus.test.utils.MultiCache;import com.yeepay.g3.core.payplus.test.utils.MultiCacheEvict;import com.yeepay.g3.core.payplus.test.utils.RedisCacheBean;@Component@Aspectpublic class CacheAspect {    @Resource public RedisCacheBean redis;        /**              * 定义缓存逻辑                        */    @Around("@annotation(com.yeepay.g3.core.payplus.test.utils.MultiCache)")    public Object cache(ProceedingJoinPoint pjp ) {        Object result=null;        Boolean cacheEnable=true;        //判断是否开启缓存        if(!cacheEnable){            try {                result= pjp.proceed();            } catch (Throwable e) {                e.printStackTrace();            }            return result;        }                Method method=getMethod(pjp);        MultiCache cacheable=method.getAnnotation(MultiCache.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(com.yeepay.g3.core.payplus.test.utils.MultiCacheEvict)")    public Object evict(ProceedingJoinPoint pjp ){    Object result=null;    Boolean cacheEnable=true;        //判断是否开启缓存        if(!cacheEnable){            try {                result= pjp.proceed();            } catch (Throwable e) {                e.printStackTrace();            }            return result;        }                Method method=getMethod(pjp);        MultiCacheEvict cacheable=method.getAnnotation(MultiCacheEvict.class);        String fieldKey =parseKey(cacheable.fieldKey(),method,pjp.getArgs());                //获取方法的返回类型,让缓存可以返回正确的类型        Class returnType=((MethodSignature)pjp.getSignature()).getReturnType();        //使用redis 的hash进行存取,易于管理        result= redis.hget(cacheable.key(), fieldKey);        if(result!=null){        redis.hdel(cacheable.key(), fieldKey);        //执行真正的删除,先更新数据库在更新缓存是最好的方案        try {result=pjp.proceed();} catch (Throwable e) {// TODO Auto-generated catch blocke.printStackTrace();}        }        return result;    }    /**     *  获取被拦截方法对象     *       *  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);    }}

3.注解如下:

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MultiCache { String key();     String fieldKey() ;     int expireTime() default 3600; }
4.redis部分略

package com.yeepay.g3.core.payplus.test.utils;import javax.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.alibaba.fastjson.JSONArray;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.jedis.ShardedJedis;import redis.clients.jedis.ShardedJedisPool;@Componentpublic class RedisCacheBean {    //注入spring-redis配置好的bean,也可以在配置文件中定义这个RedisCacheBean,里面有一个属性jedisPool,并且属性有get set操作@ResourceShardedJedisPool jedisPool;//private ShardedJedis jedis = jedisPool.getResource();    /**     * 把对象放入Hash中     */    public void hset(String key,String field,Object o){    ShardedJedis jedis =jedisPool.getResource();        jedis.hset(key,field, JSONArray.toJSONString(o));        jedisPool.returnResource(jedis);    }        /**     * 从Hash中获取对象     */    public String hget(String key,String field){    ShardedJedis 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 = JSONArray.parseObject(text, clazz);        return result;    }        /**     * 从Hash中删除对象     */    public void hdel(String key,String ... field){    ShardedJedis jedis =jedisPool.getResource();        Object result=jedis.hdel(key,field);        jedisPool.returnResource(jedis);    }    }


5.service调用部分

//使用hset的方式,getMerchantById用作key,fieldKey当做map中的key@MultiCache(key="getMerchantById",fieldKey="#name")public MerchantEntity getMerchantById(String name) {LOGGER.info("开始查询数据库");return merchantDao.getMerchantById(name);}@MultiCacheEvict(key="getMerchantById",fieldKey="#name")public void delMerchantById(String name) {LOGGER.info("开始查询数据库");merchantDao.delMerchantById(name);}

主要流程是在执行真正的删除和更新数据库之前,进入切面,在进入redis做缓存赋值操作,

对于先删除缓存还是先删除数据库,建议先删除数据库,再删除缓存。

第一种情况先删缓存在删数据库:在多线程环境下,当一个线程把缓存删掉之后,另一个线程都缓存,都不到缓存就会直接读库,读到数据后就会更新缓存,先前的线程呢,才更新数据库,会造成缓存脏读的情况,很容易产生缓存脏读。

第二种情况先删数据库再删缓存,在多线程情况下,当一个线程删除数据库,另一个线程读取缓存数据,读到的是缓存的数据,当先前一个线程删完数据库后就会更新缓存,这是缓存就正常了,产生了一次脏读,并且方式一容易出现缓存击穿数据库压力大,并且当时一的缓存时间长,如果之后再也没有关于这条缓存的操作,数据库不存在,缓存却能得到是很可怕的。

参见:http://blog.csdn.net/baiyunpeng42/article/details/53813034


原创粉丝点击