使用AOP 實現Redis快取註解,支援SPEL
阿新 • • 發佈:2019-01-07
公司專案對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" /> <propertyname="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); }
效果: