使用AOP 实现Redis缓存注解,支持SPEL

时间:2021-10-17 02:56:23

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

效果:

使用AOP 实现Redis缓存注解,支持SPEL