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

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
@Aspect
public 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 block
				e.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;

@Component
public class RedisCacheBean {
    
	//注入spring-redis配置好的bean,也可以在配置文件中定义这个RedisCacheBean,里面有一个属性jedisPool,并且属性有get set操作
	@Resource
	ShardedJedisPool 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


猜你喜欢

转载自blog.csdn.net/zhangxiaomin1992/article/details/78296214