公司项目对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); }
效果: