五一闲在家里没事干,,整点活;
前言
大概功能就是在需要缓存返回值的方法上加上注解,当方法被调用的时候自动查询缓存,命中缓存则自动返回缓存,未命中缓存则执行原本逻辑,并在执行后自动缓存新的数据
概述
大概就是通过自定义缓存Cache ,然后aop切面扫描所有使用这个注解的方法,并通过缓存枚举CacheEnum 进行管理key 值以及过期时间 ,再通过另一个注解CacheSuffix 标注需要拼接到key值之后作为特征参数的方法参数; 后缀拼接部分,因为pjp获取到的方法参数 和方法参数的注解[]数组 索引位置是对应的 ,所以检测到指定注解的那组注解的索引 就是其参数在args[]所在的索引
代码部分
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
CacheEnum cacheEnum();
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheSuffix {
}
枚举
@Getter
@AllArgsConstructor
public enum CacheEnum {
USERNAME("用户名缓存","USERNAME",5000L),
USERINFO("用户信息缓存","USERINFO",10000L),
;
private final String remark;
private final String key;
private final Long expire;
}
缓存类
@Slf4j
public class MyCaChe {
public static MyCaChe caChe = new MyCaChe();
private static ConcurrentMap<String, Container> data;
public Object get(String key) {
Container container = data.get(key);
if (ObjectUtil.isEmpty(container))
return null;
Long ttl = container.getTtl();
long cu = System.currentTimeMillis();
if (ttl < cu && container.getTtl() != -1) {
data.remove(key);
return null;
}
return container.getData();
}
public void set(String key, Object o, Long timeout) {
data.put(key, Container.builder().data(o).ttl(System.currentTimeMillis() + timeout).build());
}
public void set(String key, Object o) {
data.put(key, Container.builder().data(o).ttl(-1L).build());
}
public MyCaChe() {
data = new ConcurrentHashMap<>();
new Thread(() -> {
while (true)
clear();
}).start();
}
public synchronized void clear() {
try {
Set<Map.Entry<String, Container>> entries = data.entrySet();
int startCount = entries.size();
data = entries.stream()
.filter(en -> en.getValue().getTtl() == -1L || en.getValue().getTtl() > System.currentTimeMillis())
.collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue));
log.info("执行缓存清理,本次清理了:{}个失效缓存,现有缓存数量:{}", startCount - data.size(), data.size());
wait(60000);
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException("等待出现异常");
}
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
class Container {
private Object data;
private Long ttl;
}
AOP
@Aspect
@Component
@AllArgsConstructor
public class CacheAop {
@Around("@annotation(com.do.leProject.code.annotation.Cache)")
public Object enumCacheAroundAspect(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Cache cache = method.getDeclaredAnnotation(Cache.class);
if (cache == null)
return pjp.proceed();
CacheEnum caCheEnum = cache.cacheEnum();
String cacheKey = getCacheKey(cache, pjp);
Object cacheData = getCache(cacheKey);
if (ObjectUtil.isNotEmpty(cacheData))
return cacheData;
Object methodData = pjp.proceed();
setCache(cacheKey, methodData, caCheEnum.getExpire());
return methodData;
}
public String getCacheKey(Cache cache, ProceedingJoinPoint pjp) {
StringJoiner keyJoiner = new StringJoiner(";;");
keyJoiner.add(cache.cacheEnum().getKey());
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Object[] args = pjp.getArgs();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotation = parameterAnnotations[i];
for (Annotation an : annotation) {
if (an instanceof CacheSuffix)
keyJoiner.add(String.valueOf(args[i]));
}
}
return keyJoiner.toString();
}
private Object getCache(String key) {
return MyCaChe.caChe.get(key);
}
private void setCache(String key, Object data, Long exp) {
MyCaChe.caChe.set(key, data, exp);
}
}
测试部分代码
Controller
@RestController
@RequestMapping("/cache")
@AllArgsConstructor
public class CacheEnumTestController {
private final CacheEnumService cacheEnumService;
@GetMapping("/getName/{id}")
public String getName(@PathVariable String id) {
return cacheEnumService.getName(id,"name",3);
}
}
Service
@Service
public class CacheEnumService {
@Cache(cacheEnum = CacheEnum.USERNAME)
public String getName(@CacheSuffix String id,String nas,int age) {
StringJoiner name = new StringJoiner(",", "【", "】");
name.add("zhangsan");
name.add("id:" + id);
name.add("age:" + new Random().nextInt(100));
return name.toString();
}
}
补充
缓存容器部分,这里只是临时写的测试工具,实际中使用redis或者缓存工具类,不要整那个死循环清理; 枚举只需要按照规则定义即可; 缓存读取写入部分,需要按照使用的工具或者中间件重写;
不指定缓存key的后缀 时默认就使用枚举的key 值了;
日志上报部分
日志上报封装实体
这个上报内容视情况而定
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class RequestLogReport {
private Integer httpStatus;
private String reqUri;
private String reqRemotePath;
private Object requestParam;
private Long responseTime;
private LocalDateTime reqDateTime;
private String deviceName;
}
日志上报以及打印部分AOP代码
@Aspect
@Component
@AllArgsConstructor
public class RequestLogAop {
private static final Logger log = LoggerFactory.getLogger(RequestLogAop.class);
private static final Map<String, String> ENV = System.getenv();
private final HttpServletRequest req;
private final HttpServletResponse rsp;
@Pointcut("execution(public * com.do.leProject.codeStyle.code.controller.*Controller.*(..))")
public void requestLogoPoint() {
}
@Around("requestLogoPoint()")
public Object enumCacheAroundAspect(ProceedingJoinPoint pjp) throws Throwable {
log.info("【Request】:Url:{};请求参数:{}", req.getRequestURI(), pjp.getArgs());
long start = System.currentTimeMillis();
Object result = pjp.proceed();
log.info("【Response[{}]】:响应结果:{}", rsp.getStatus(), result);
this.requestReport(RequestLogReport.builder()
.deviceName(ENV.get("USERDOMAIN"))
.reqRemotePath(req.getRemoteAddr())
.reqUri(req.getRequestURI())
.requestParam(pjp.getArgs())
.reqDateTime(LocalDateTime.now())
.httpStatus(rsp.getStatus())
.responseTime(System.currentTimeMillis() - start)
.build());
return result;
}
@Async
void requestReport(RequestLogReport requestLogReport) {
log.info("请求日志上报=>{}", JSONUtil.toJsonStr(requestLogReport));
}
}
效果
结语
啊啊啊啊 。。。又是个无聊的五一啊。。。
|