传送门:使用切面注解编程实现redis模糊删除数据之二使用spel表达式
之前使用spring-redis,发现没有根据模糊查询删除redis,局限性很大,比如我有两个权限表,模块权限表baseModule,和按钮权限表baseButton。我把权限进行了缓存,然后在登陆时删除缓存,
模块权限的保存名是baseModulePermissionList+#userId,
按钮权限表的保存名是baseButtonPermissionList+#userId+#moduleId,
登陆时可以获得用户id#userId,所以模块权限列表可以直接删除,而按钮权限表却不能,
原生的支持只有设置@CacheEvict(value="baseButtonPermissionList",allEntries=true)删除所有的按钮权限列表,这明显不合理,总不能有一个人登陆就删除所有其他人的缓存。
于是我在网上寻找资料,看到了一个使用切面编程aop实现ehcache模糊字段删除的文章,接触了神奇的aop。下面是我的实现过程。
首先创建自定义注解@CacheRemove
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ java.lang.annotation.ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRemove {
String value() default"";
String[] key() default{};
}
@Target表示注解是加在什么类型上的,比如方法,类,参数等,@Retention表示注解生效的范围,只有RUNTIME才能在java运行时生效,其他有编译前生效,编译后运行前生效。
然后是导入aop编程相关的包,我发现spring-boot-starter-data-jpa中已经自带了spring-aop
接着是切面类
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import com.kq.highnet2.framework.base.common.annotation.CacheRemove;
@Aspect
@Component
public class CacheRemoveAspect {
Logger logger=LoggerFactory.getLogger(this.getClass());
@Resource(name = "redisTemplate")
RedisTemplate<String, String> redis;
@Pointcut(value = "(execution(* *.*(..)) && "//截获标有@CacheRemove的方法
+ "@annotation(com.kq.highnet2.framework.base.common.annotation.CacheRemove))")
private void pointcut() {}
@AfterReturning(value = "pointcut()")//切面在截获方法返回值之后
private void process(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();//切面截获方法的参数
Method method = signature.getMethod();//切面截获方法
CacheRemove cacheRemove = method.getAnnotation(CacheRemove.class);//获得注解
if (cacheRemove != null){
String value = cacheRemove.value();//暂时没用
String[] keys = cacheRemove.key(); //需要移除的正则key
for(String key:keys) {
List<String> list = descFormat(key);//获得key里面"{?}"的值,我都用数字比如baseButtonList{0}*
for (String s : list) {
String arg = (String) args[Integer.valueOf(s)];//获得相应的参数
key = key.replace("{"+s+"}", arg); //用参数的值替换key中的{数字}
}
Set<String> keys2 = redis.keys(key);//获得redis中符合正则的缓存
redis.delete(keys2);//删除缓存
logger.info("删除缓存:"+key);
}
}
}
/**
* 获取字符串中的"{#arg}",然后提取成集合
* @param desc
* @return
*/
private static List<String> descFormat(String desc){
List<String> list = new ArrayList<>();
Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}");
Matcher matcher = pattern.matcher(desc);
while(matcher.find()){
String t = matcher.group(1);
list.add(t);
}
return list;
}
}
然后是在方法上标上@CacheRemove
@Caching(evict= {
@CacheEvict(value="baseModulePermissionList",key="'baseModulePermissionList'+#userId"),
})
@CacheRemove(value="deletePermission",key = {"baseButtonPermissionList{0}*","baseViewPermissionList{0}*"})
public void evictPermission(String userId) {
}
这里我专门写了一个删除缓存的方法,里面没有任何逻辑。
有一点需要注意,你在本类调用这个方法,aop是不起作用的,比如:
public BaseUserModel checkLogin(String account, String password, boolean beNewAccessKey) {
// 1:校验用户名和密码是否为空
// BaseResult baseResult = new BaseResult();
{
if (StringUtils.isEmpty(account) || StringUtils.isEmpty(password)) {
throw new RuntimeException("账号或是密码为空!");
}
}
// 2:查询当前用户
BaseUserModel baseUser = baseUserDao.findByAccount(account);
// 3:校验用户是否存在,检查用户名和口令是否正常
{
if (baseUser == null) {
throw new RuntimeException("账号或是密码不正确!");
}
if (baseUser.getPassword().equals(password)==false) {
throw new RuntimeException("账号或是密码不正确!");
}
if(baseUser.hasValidUser()==false){
throw new RuntimeException("当前账号异常或不在有效状态");
}
}
//4:设置访问日志(指Login)
{
baseUser.recordVisitLog();
}
// 4:生成新的访问令牌(如果需要)
{
if (beNewAccessKey == true) {
baseUser.newAccessKey();
this.baseUserDao.save(baseUser);
}
}
//更新最后登录时间
baseUser.setLastVisit(new Date());
baseUserDao.save(baseUser);
evictPermission(baseUser.userId());/////在这里调用是没有用的/////////////////////////////<<<-------------------
return baseUser;
}
@Caching(evict= {
@CacheEvict(value="baseModulePermissionList",key="'baseModulePermissionList'+#userId"),
})
@CacheRemove(value="deletePermission",key = {"baseButtonPermissionList{0}*","baseViewPermissionList{0}*"})
public void evictPermission(String userId) {
}
只有在别的类里调用这个方法才行,所以我在控制层调用了这个方法。这也是我尝试多次后发现的,之前的事务注解也有这个现象,我使用@Transactional(propagation=Propagation.REQUIRES_NEW)时,如果标在本类的方法上就不生效,我只能新建一个service类,把方法写到这个新的service类中,然后就生效了,现在看来,事务也是借助了切面编程,这个应该是共性。
测试过后完美运行
注意!!!发现一个坑,aop只能拦截实现类上的注解,不能拦截接口上的注解,而spring-redis是可以加在接口上的。看来spring-redis的实现不是aop这么简单的