背景
在查詢類開發中我們有使用緩存的場景,一般可以使用redis作為緩存,來緩解數據庫如MySQL的壓力。使用緩存的步驟為:
“
(1)從Redis緩存中獲取數據,如果存在數據,直接返回值。
(2)如果不存在,執行數據庫的查詢方法
(3)將數據庫中的值放入緩存
”
NO CODE NO BB,代碼如下
//a.從緩存中獲取
String value = redisTemplate.opsForValue().get(key);
if (value != null) {
log.info("從緩存中讀取到值:{}", value);
return value;
}
//b.從數據庫中查詢
List<Member> members = memberMApper.listByName(name);
//c.同步value到緩存
value = JSONArray.toJSONString(members);
redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);
return value;
如上代碼,這里有個問題,我們只是要做個查詢而已,也就是只要b行的代碼,其他代碼不是業務代碼,不應該由開發人員去操心。那我們何不用注解的形式代替a和c代碼呢。
使用SpringBoot的緩存注解
SpringBoot提供了現成可用的緩存注解@Cacheble。
配置類開啟緩存注解
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
...
}
...
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60)) //緩存過期時間
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
使用@Cacheable注解
@Cacheable(value = "member",key = "#name")
public List<Member> listByName(String name) {
return memberMapper.listByName(name);
}
測試
@Test
void listByName() {
String name = "zhouzhou";
List<Member> members = memberController.listByName(name);
log.info("members:{}",members);
}
控制臺結果
members:[Member(id=1805590839001216, name=zhouzhou, code=109, annotationParam=null)]
上面代碼發現使用Spring緩存注解的緩存失效時間還要在配置類中進行配置。于是我在想為什么這個失效時間不做成注解的這一項屬性呢,這樣自定義失效時間就比較方便了。
自定義緩存注解
注解定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomizeCache {
String key();
String value();
long expireTimes() default 120L; //默認過期時間120s
int semaphoreCount() default Integer.MAX_VALUE; //默認限制線程并發數
}
參數說明如下:
“
key():緩存的key,一般是一個動態的參數值
vaule():緩存的value,value::key為Redis緩存中拼接的KEY
expireTimes():緩存失效時間,默認
semaphoreCount():共享鎖,并發下允許訪問的線程數,用于保護數據庫。默認為Integer.MAX_VALUE
”
AOP注解開發
首先創建切面類
@Component
@Aspect
@Slf4j
public class CacheAspect {
...
}
創建橫切面,為注解CustomizeCache添加功能。
@Pointcut("@annotation(com.lvshen.demo.redis.cache.CustomizeCache)")
public void cachePointcut() {
}
開發緩存功能,定義@Around
@Around("cachePointcut()")
public Object doCache(ProceedingJoinPoint point) {
...
}
獲取方法上注解的內容
Method method = point.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
CustomizeCache annotation = method.getAnnotation(CustomizeCache.class);
String keyEl = annotation.key();
String prefix = annotation.value();
long expireTimes = annotation.expireTimes();
int semaphoreCount = annotation.semaphoreCount();
解析SpringEL表達式
Object[] args = point.getArgs();
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i].toString());
}
拼接Redis KEY
//解析
String key = prefix + "::" + expression.getValue(context).toString();
判斷緩存中是否存在
value = redisTemplate.opsForValue().get(key);
if (value != null) {
log.info("從緩存中讀取到值:{}", value);
return value;
}
自定義組件-創建限流令牌
semaphore = new Semaphore(semaphoreCount);
boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS);
if (!tryAcquire) {
//log.info("當前線程【{}】獲取令牌失敗,等待其他線程釋放令牌", Thread.currentThread().getName());
throw new RuntimeException(String.format("當前線程【%s】獲取令牌失敗,等帶其他線程釋放令牌", Thread.currentThread().getName()));
}
如果緩存沒有數據,則執行原本方法。
value = point.proceed();
同步value到緩存
redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);
最后釋放令牌
} finally {
if (semaphore == null) {
return value;
} else {
semaphore.release();
}
}
調用注解
@CustomizeCache(value = "member", key = "#name")
public List<Member> listByNameSelfCache(String name) {
return memberMapper.listByName(name);
}
測試
@Test
void testCache() {
String name = "lvshen99";
List<Member> members = memberService.listByNameSelfCache(name);
log.info("members:{}",members);
}
測試結果
members:[Member(id=15, name=lvshen99, code=200, annotationParam=null)]
Redis上顯示
我們也可以自定義緩存失效時間,如設置失效時間
@CustomizeCache(value = "member", key = "#name",expireTimes = 3600)
public List<Member> listByNameSelfCache(String name) {
return memberMapper.listByName(name);
}
失效時間為,如圖,顯示是因為截圖的時候過了。
源碼地址如下:
完整源碼Github地址:https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/JAVA/com/lvshen/demo/redis/cache