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

效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值