1. 程式人生 > >使用切面註解程式設計實現redis模糊刪除資料之一

使用切面註解程式設計實現redis模糊刪除資料之一

之前使用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這麼簡單的