SpringBoot使用AOP實現自定義介面快取
阿新 • • 發佈:2019-02-11
一、引入pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
二、新建兩個註解
(1)該註解表示需要快取
package com.lzc.aopcache.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * 自定義快取註解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LzcCache { String cacheName() default ""; String key() default ""; long timeOut() default 0; TimeUnit timeUnit() default TimeUnit.HOURS; }
(2)該註解表示需要清除快取
package com.lzc.aopcache.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * 自定義清除快取註解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LzcCacheEvict { String cacheName() default ""; String key() default ""; }
三、新建兩個切面
(1)快取切面
package com.lzc.aopcache.aspect;
import com.lzc.aopcache.annotation.LzcCache;
import com.lzc.aopcache.utils.JsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class LzcCacheAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("execution(* com.lzc.aopcache.*.*.*(..))")
public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {
Class<?> cls = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
Map<String, Object> map = isChache(cls, methodName);
boolean isCache = (boolean)map.get("isCache");
if (isCache) {
String cacheName = (String) map.get("cacheName"); // 快取名字
String key = (String) map.get("key"); // 自定義快取key
long timeOut = (long) map.get("timeOut"); // 過期時間, 0代表永久有效
TimeUnit timeUnit = (TimeUnit) map.get("timeUnit"); // 過期時間單位
Class<?> methodReturnType = (Class<?>)map.get("methodReturnType"); // 方法的返回型別
Method method = (Method)map.get("method"); // 方法
String realCacheName = "";
// 判斷cacheName是否為空,如果cacheName為空則使用預設的cacheName
if(cacheName.equals("")) {
realCacheName = cls.getName() + "." + methodName;
} else {
realCacheName = cacheName;
}
String realKey = "";
// 判斷key是否為空, 如果為空則使用預設的key
if (key.equals("")) {
realKey = realCacheName + "::" + defaultKeyGenerator(jp);
} else {
realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
}
// 判斷快取中是否存在該key, 如果存在則直接從快取中獲取資料並返回
if (stringRedisTemplate.hasKey(realKey)) {
String value = stringRedisTemplate.opsForValue().get(realKey);
return JsonUtil.toBean(methodReturnType, value);
} else {
Object result = jp.proceed();
// 將返回結果儲存到快取中
if (timeOut == 0) {
stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result));
} else {
stringRedisTemplate.opsForValue().set(realKey, JsonUtil.toJson(result), timeOut, timeUnit);
}
return result;
}
}
return jp.proceed();
}
/**
* 自定義生成key,使用方法中的引數作為key
*/
private String defaultKeyGenerator(ProceedingJoinPoint jp) {
// 獲取所有引數的值
List<String> list = new ArrayList<>();
Object[] args = jp.getArgs();
for (Object object : args) {
list.add(object.toString());
}
return list.toString();
}
/**
* 獲取快取的key
* key 定義在註解上,支援SPEL表示式
*/
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);
}
private Map<String, Object> isChache(Class<?> cls, String methodName) {
boolean isCache = false;
Map<String, Object> map = new HashMap<>();
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCache.class)) {
LzcCache lzcCache = method.getAnnotation(LzcCache.class); // 獲取方法上的註解
Class<?> methodReturnType = method.getReturnType(); // 獲取方法的返回型別
map.put("cacheName", lzcCache.cacheName());
map.put("key", lzcCache.key());
map.put("timeOut", lzcCache.timeOut());
map.put("timeUnit", lzcCache.timeUnit());
map.put("methodReturnType", methodReturnType);
map.put("method", method);
isCache = true;
break;
}
}
map.put("isCache", isCache);
return map;
}
}
(2)清除快取切面
package com.lzc.aopcache.aspect;
import com.lzc.aopcache.annotation.LzcCacheEvict;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Aspect
@Component
public class LzcCacheEvictAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("execution(* com.lzc.aopcache.*.*.*(..))")
public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {
Class<?> cls = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
Map<String, Object> map = isChacheEvict(cls, methodName);
boolean isCacheEvict = (boolean)map.get("isCacheEvict");
if (isCacheEvict) {
String cacheName = (String) map.get("cacheName"); // 快取名字
String key = (String) map.get("key"); // 自定義快取key
Method method = (Method)map.get("method"); // 方法
String realCacheName = "";
// 判斷cacheName是否為空,如果cacheName為空則使用預設的cacheName
if(cacheName.equals("")) {
realCacheName = cls.getName() + "." + methodName;
} else {
realCacheName = cacheName;
}
String realKey = "";
// 判斷key是否為空, 如果為空則使用預設的key
if (key.equals("")) {
realKey = realCacheName + "::" + defaultKeyGenerator(jp);
} else {
realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
}
// 判斷快取中是否存在該key, 如果存在則直接從快取中刪除該資料
if (stringRedisTemplate.hasKey(realKey)) {
stringRedisTemplate.delete(realKey);
}
}
return jp.proceed();
}
/**
* 自定義生成key,使用方法中的引數作為key
*/
private String defaultKeyGenerator(ProceedingJoinPoint jp) {
// 獲取所有引數的值
List<String> list = new ArrayList<>();
Object[] args = jp.getArgs();
for (Object object : args) {
list.add(object.toString());
}
return list.toString();
}
/**
* 獲取快取的key
* key 定義在註解上,支援SPEL表示式
*/
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);
}
private Map<String, Object> isChacheEvict(Class<?> cls, String methodName) {
boolean isCacheEvict = false;
Map<String, Object> map = new HashMap<>();
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCacheEvict.class)) {
LzcCacheEvict lzcCacheEvict = method.getAnnotation(LzcCacheEvict.class); // 獲取方法上的註解
map.put("cacheName", lzcCacheEvict.cacheName());
map.put("key", lzcCacheEvict.key());
map.put("method", method);
isCacheEvict = true;
break;
}
}
map.put("isCacheEvict", isCacheEvict);
return map;
}
}
四、使用
@GetMapping("/cache")
@LzcCache(cacheName = "user",key = "#user.username", timeOut = 10, timeUnit = TimeUnit.MINUTES)
public ResultVO cahce(User user) {
System.out.println("進來了");
return ResultVOUtil.success(user);
}
@GetMapping("/cache1")
@LzcCacheEvict(cacheName = "user",key = "#user.username")
public ResultVO cahce1(User user) {
return ResultVOUtil.success("清除成功");
}