傳送門:使用切面註解編程實現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這麼簡單的