1、批量删除缓存实现 1.1、AOP环境集成最近发了关于一篇关于Ehcache缓存的文章,发现评论有问到如果方法参数是一个集合,注解@CacheEvict怎么实现批量删除缓存?当时没有考虑到这种情况也就不了了之了。马上查了一下资料发现@CacheEvict只支持删除单个key,要想批量删除只能自己去实现,于是抽时间研究了下在执行方法之前通过AOP拿到参数列表,批量删除缓存后再执行目标方法。完美解决了缓存批量删除问题,在这里分享给大家,让大家体验下AOP运用场景。
1、引入AOP支持jar包依赖
org.springframework.boot spring-boot-starter-aop 2.5.6
2、Pointcut切入点定义。这里我们采用自定义注解的方式定义Advice将要发生的地方(个人觉得自定义注解比较方便,execution不太友好)。例如基于批量删除缓存这个功能我们可以自定义的注解为@CacheBatchEvict。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheBatchEvict {
String[] cacheNames() default {};
String key() default "";
boolean isClearAll() default false;
}
假如某批量删除数据方法,集合元素都是缓存的key,删除数据的同时我们也想批量删除缓存,就可以使用这个注解代替@CacheEvict。
3、切面类定义
@Aspect
@Component(value = "cacheAspect")
public class CacheAspect {
private final org.apache.logging.log4j.Logger log = Logger.getLogger(CacheAspect.class);
@Autowired
private CacheManager cacheManager;
@Pointcut("@annotation(com.laizhenghua.cache.annotation.CacheBatchEvict)")
public void pointcut() {
}
@Around("pointcut()")
public Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 1.获取注解参数值 SpEL表达式
CacheBatchEvict cacheBatchEvict = methodSignature.getMethod().getAnnotation(CacheBatchEvict.class);
String[] cacheNames = cacheBatchEvict.cacheNames();
// 清空所有缓存
if (cacheBatchEvict.isClearAll()) {
for (String cacheName : cacheNames) {
net.sf.ehcache.Cache cache = (Cache) Objects.requireNonNull(cacheManager.getCache(cacheName)).getNativeCache();
cache.removeAll();
}
}
String spEL = cacheBatchEvict.key();
if (!StringUtils.hasText(spEL)) {
log.warn("@CacheBatchEvict key is null");
return null;
}
// 2.获取目标方法参数
List
1.2、在方法中使用注解
我们发现自定义的注解@CacheBatchEvict不仅作为APO切入点的描述,还能嵌入一些补充信息如缓存组件名称cacheName、缓存的key信息等供我们在切面类中灵活处理,这就是自定义注解的好处。
如何在方法中使用注解呢?在接口实现类的方法中(因为JDK的动态代理必须要有接口)使用@CacheBatchEvict代替@CacheEvict即可。例如:
@Override
@CacheBatchEvict(cacheNames = {"userEntityCache"}, key = "#idList") // spEL表达式描述方法参数名称
public void delete(List idList) {
log.info("delete all by collection");
// 执行删除逻辑
}
2、测试
2.1、获取所有缓存的key
因为我们集成的缓存产品是Ehcache,所以通过Spring缓存抽象获取所有key信息时,需要把缓存组件转成net.sf.ehcache.Cache,如:
@RestController
@RequestMapping(value = "cache")
public class CacheController {
@Autowired
private CacheManager cacheManager;
@RequestMapping(value = "/getKeyList", method = RequestMethod.GET)
public R getCacheKeyList(@RequestParam(value = "cacheName", required = false, defaultValue = "userEntityCache") String cacheName) {
net.sf.ehcache.Cache cache = (Cache) Objects.requireNonNull(cacheManager.getCache(cacheName)).getNativeCache();
List keyList = cache.getKeys();
return R.ok().put("data", keyList);
}
}
2.2、测试方法编写
为了测试方便,我们把实体对象ID作为缓存的key。
1、新增保存方法,保存数据并把方法返回结果存入缓存中
controller层
@RequestMapping(value = "/save/{id}/{name}", method = RequestMethod.GET)
public R save(@PathVariable("id") Integer id, @PathVariable("name") String name) {
UserEntity entity = userService.save(id, name);
return R.ok().put("data", entity);
}
service层,注意接口已经省略,直接给出接口实现类里的方法
@Override
@Cacheable(value = "userEntityCache", key = "#root.args[0]") // 使用参数id作为换行key
public UserEntity save(Integer id, String name) {
UserEntity entity = new UserEntity(id, name);
log.info("save to ehcache -> " + entity.toString());
// 保存至数据库里
return entity;
}
2、新增获取实体方法,用于测试获取缓存中的数据
controller层
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public R getById(@PathVariable("id") Integer id) {
UserEntity entity = userService.getById(id);
return R.ok().put("data", entity);
}
service层
@Override
@Cacheable(value = "userEntityCache", key = "#root.args[0]") // 使用参数id作为换行key
public UserEntity getById(Integer id) {
log.info("query entity by id = " + id);
// 如果返回null,说明缓存中数据已经没有了
return null;
}
3、编写批量删除方法
controller层
@RequestMapping(value = "/delete", method = RequestMethod.POST)
public R delete(@RequestParam("idList") List idList) {
userService.delete(idList);
return R.ok();
}
service层
@Override
@CacheBatchEvict(cacheNames = {"userEntityCache"}, key = "#idList") // spEL表达式描述方法参数名称
public void delete(List idList) {
log.info("delete all by collection");
// 执行删除逻辑
}
2.3、测试
1、调用保存接口,保存两条测试数据
如
2、保存到缓存中后,测试获取有没有走缓存
我们发现已经获取到数据了,但是后台并没有输出任何日志,说明已经走缓存了。
3、调用批量删除方法,看看能否也能批量删除缓存
OK方法已经执行成功了,在调用获取方法,观看日志输出情况!
日志输出情况
OK,成功删除缓存。
到这里内容也就结束了,简单做个小结,删除功能也很简单就是在删除数据之前通过AOP拿到方法参数信息,然后手动清理缓存。重要的不是功能,而是一种思想,要灵活运用AOP的思想,在目标方法之前或执行之后做一些与业务逻辑无关的额外功能。
END


