本文主要講述如何透過SpringBoot+Redis實現介面級別快取資訊
背景
近期因為一直在處理公司的老專案,恰好碰到產品說頁面有一些資訊展示慢,簡單看了一下頁面介面,發現查詢的是系統中幾張大表(資料量在千萬級別),還會關聯一些其他的表,導致介面效能極差,但是由於這些資訊也不存在"及時性"這麼一說,便想著透過介面快取來控制
相關技術
jdk 1.8
reids 5.0.7
實現思路
透過註解來標識需要快取的介面,依據註解的內容去找到對應的建造者,透過建造者來找到具體去執行的類,最終達可擴充套件+快取的效果
註解相關程式碼
package com.com.example.springdemo;
import com.com.example.springdemo.aspect.enums.RedisCacheEnums;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @CreateAt: 2023-11-3 11:25:37
* @ModifyAt: 2023-11-3 11:25:37
* @Version 1.0
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCache {
/**
* 快取的key
**/
String key() default "";
/**
* 引數型別
*
* @return
*/
RedisCacheEnums type();
/**
* 快取時長,預設-1表示永久有效
**/
int time() default 300;
/**
* 快取時長單位,預設單位秒
**/
TimeUnit timeType() default TimeUnit.SECONDS;
}
列舉相關程式碼
package com.example.springdemo.enums;
import java.util.Arrays;
/**
* 快取型別
*
* @CreateAt: 2023-11-3 11:26:22
* @ModifyAt: 2023-11-3 11:26:22
* @Version 1.0
*/
public enum RedisCacheEnums {
QUERY_MEMBER_INFO(1, "查詢會員資訊"),
;
private Integer code;
private String type;
RedisCacheEnums(Integer code, String type) {
this.code = code;
this.type = type;
}
public static RedisCacheEnums getByCode(int code) {
return Arrays.stream(RedisCacheEnums.values())
.filter(item -> item.getCode().intValue() == code)
.findFirst()
.orElse(null);
}
public Integer getCode() {
return code;
}
public String getType() {
return type;
}
}
切換相關程式碼
package com.example.springdemo.aspect;
import com.example.springdemo.RedisCacheProvider;
import com.example.springdemo.handler.RedisCacheHandler;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* @CreateAt: 2023-11-3 11:27:20
* @ModifyAt: 2023-11-3 11:27:20
* @Version 1.0
*/
@Aspect
@Slf4j
@Component
@AllArgsConstructor
public class RedisCacheAspect {
@Pointcut("@annotation(com.example.springdemo.RedisCache)")
public void annotationPoint() throws Throwable {
}
private RedisCacheProvider redisCacheProvider;
/**
* 卻面切入點
* implements Serializable 物件需要繼承
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around(value = "annotationPoint()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
MethodSignature method = (MethodSignature) proceedingJoinPoint.getSignature();
RedisCache redisCache = method.getMethod().getAnnotation(RedisCache.class);
Object[] args = proceedingJoinPoint.getArgs();
RedisCacheHandler apply = redisCacheProvider.apply(redisCache.type());
Boolean hasKey = apply.existHandler(args, redisCache.key());
if (hasKey) {
return apply.queryHandler(args, redisCache.key());
} else {
Object result = proceedingJoinPoint.proceed();
apply.handler(redisCache.type(), args, result, redisCache.time(), redisCache.timeType(), redisCache.key());
return result;
}
} catch (Exception e) {
log.info("RedisCacheAspect Error:{}", e.toString());
return proceedingJoinPoint.proceed();
}
}
}
建造者和相關hannder對應程式碼
package com.example.springdemo;
import com.example.springdemo.enums.RedisCacheEnums;
import com.example.springdemo.handler.RedisCacheHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.util.List;
/**
* 快取提供者
* @CreateAt: 2023-11-3 11:28:42
* @ModifyAt: 2023-11-3 11:28:42
* @Version 1.0
*/
@Component
@AllArgsConstructor
@Slf4j
public class RedisCacheProvider {
private List<RedisCacheHandler> handlers;
public RedisCacheHandler apply(RedisCacheEnums type) {
RedisCacheHandler redisCacheHandler = handlers.stream().filter(x -> x.support(type)).findFirst().orElse(null);
Assert.notNull(redisCacheHandler, "未找到對應的處理器");
return redisCacheHandler;
}
}
package com.example.springdemo.handler;
import com.example.springdemo.enums.RedisCacheEnums;
import java.util.concurrent.TimeUnit;
/**
* @CreateAt: 2023-11-3 11:29:39
* @ModifyAt: 2023-11-3 11:29:39
* @Version 1.0
*/
public interface RedisCacheHandler {
/**
* 是否支援處理
*
* @param type
* @return
*/
boolean support(RedisCacheEnums type);
/**
* 查詢快取資訊
*
* @param args
* @param originalKey
* @return
*/
Object queryHandler(Object[] args, String originalKey) throws Exception;
/**
* 快取資訊是否存在
*
* @param args
* @param originalKey
* @return
*/
Boolean existHandler(Object[] args, String originalKey) throws Exception;
/**
* 生成快取資訊
*
* @param type 型別
* @param args 引數
* @param result 結果
* @param time 時間
* @param timeType 時間型別
* @param originalKey
*/
void handler(RedisCacheEnums type, Object[] args, Object result, Integer time, TimeUnit timeType, String originalKey) throws Exception;
}
package com.example.springdemo.handler;
import com.example.springdemo.enums.RedisCacheEnums;
import com.example.springdemo.common.utils.RedisUtil;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @CreateAt: 2023-11-3 11:30:30
* @ModifyAt: 2023-11-3 11:30:30
* @Version 1.0
*/
@Slf4j
@Service
@AllArgsConstructor
public class MemberHandler implements RedisCacheHandler {
private final String redisKey = "test";
// 切換成自己專案使用的redis工具類即可
private RedisUtil resdisUtil;
/**
* 是否支援處理
*
* @param type
* @return
*/
@Override
public boolean support(RedisCacheEnums type) {
return RedisCacheEnums.QUERY_MEMBER_INFO.equals(type);
}
/**
* 查詢快取資訊
*
* @param args
* @param originalKey
* @return
*/
@Override
public Object queryHandler(Object[] args, String originalKey) throws Exception {
String key = getKey(args, originalKey);
return resdisUtil.get(key);
}
/**
* 查詢快取資訊
*
* @param args
* @param originalKey
* @return
*/
@Override
public Boolean existHandler(Object[] args, String originalKey) throws Exception {
String key = getKey(args, originalKey);
return resdisUtil.hasKey(key);
}
/**
* 生成操作記錄物件
*
* @param type
* @param args
* @param result
* @param time
* @param timeType
* @param originalKey
* @return
*/
@Override
public void handler(RedisCacheEnums type, Object[] args, Object result, Integer time, TimeUnit timeType, String originalKey) throws Exception {
String key = getKey(args, originalKey);
resdisUtil.setByTime(key, result, time, timeType);
}
/**
* 獲取Key資訊
*
* @param args
* @return
*/
private String getKey(Object[] args, String originalKey) throws Exception {
try {
Object omiMemberInquiryVO = (Object ) args[0];
// 拼裝快取key資訊
String key = "test";
log.info("RedisCacheAspect key:{}",key);
return key;
} catch (Exception e) {
log.info("RedisCacheAspect 拼裝Key引數異常:{},e:{}", new Gson().toJson(args), e.toString());
throw new Exception("拼裝Key引數異常");
}
}
}
手動ps:可能很多人都會問為什麼不用Spring自帶的,而需要自己去寫,主要原因還是這是一個老系統,壓根找不全對資料進行修改、刪除的地方
如有哪裡講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧?