1.: Redis 依存関係とカスタム アノテーション @DataChange を導入し、コントローラーを実装します。
<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>
<version>2.2.6.RELEASE</version>
</dependency>
設定ファイルMyCacheConfig
@Configuration
@EnableCaching //开启spring缓存
public class MyCacheConfig extends CachingConfigurerSupport {
/**
* @Description: 使用@Cacheable注解的时候会将返回的对象缓存起来,默认缓存的值是二进制的,
* 为此我们自定义序列化配置,改成JSON格式的
*/
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))// 设置缓存有效期一小时
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
/**
*自己的RedisTemplate
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(factory);
//序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);过时
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... objects) {
//1.缓冲
StringBuilder sb = new StringBuilder();
//2.类名
sb.append(target.getClass().getSimpleName());
//3.方法名
sb.append(method.getName());
//4.参数值
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
これにより、 @Cacheable アノテーションが使用され、デフォルトのキャッシュ値がbinaryである場合に返されたオブジェクトがキャッシュされるという問題が解決されます。
2. カスタム注釈の使用
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
//普通的操作说明
String name() default "";
//spel表达式的操作说明
String spelName() default "";
}
注: (spel 式を使用できる必要があります)
3.ClearAndReloadCacheAspect によるアスペクトの二重削除の遅延
import cn.hutool.core.util.StrUtil;
import com.redisService.cache.ClearAndReloadCache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Set;
@Aspect
@Component
public class ClearAndReloadCacheAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 切入点
*切入点,基于注解实现的切入点 加上该注解的都是Aop切面的切入点
*
*/
@Pointcut("@annotation(com.redisService.cache.ClearAndReloadCache)")
public void pointCut(){
}
/**
* 环绕通知
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
* @param proceedingJoinPoint
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("----------- 环绕通知 -----------");
Signature signature1 = proceedingJoinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature1;
Method targetMethod = methodSignature.getMethod();//方法对象
ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定义注解的方法对象
String name = "null";
//获取自定义注解的值,是否使用el表达式
if (annotation != null) {
if (StrUtil.isNotBlank(annotation.name())) {
name = annotation.name();
}
//注解上的描述
if (StrUtil.isNotBlank(annotation.spelName())) {
name = SpelUtil.generateKeyBySpEL(annotation.spelName(), proceedingJoinPoint);
}
}
// Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//模糊定义key的删除
Set<String> keys = stringRedisTemplate.keys(name);//確切刪除
stringRedisTemplate.delete(keys);//模糊删除redis的key值
System.out.println("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName()+",keys="+name);
//执行加入双删注解的改动数据库的业务 即controller中的方法业务
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//开一个线程 延迟1秒(此处是1秒举例,可以改成自己的业务)
// 在线程中延迟删除 同时将业务代码的结果返回 这样不影响业务代码的执行
String finalName = name;
new Thread(() -> {
try {
Thread.sleep(1000);
// Set<String> keys1 = stringRedisTemplate.keys("*" + finalName + "*");//模糊删除
Set<String> keys1 = stringRedisTemplate.keys(finalName);//確切刪除
stringRedisTemplate.delete(keys1);
System.out.println("-----------1秒钟后,在线程中延迟删除完毕 -----------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return proceed;//返回业务代码的值
}
}
使用されるSpelUtil メソッド
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
public class SpelUtil {
/**
* 用于SpEL表达式解析.
*/
private static SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
public static String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
// 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(spELString);
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 表达式从上下文中计算出实际参数值
/*如:
@annotation(key="#student.name")
method(Student student)
那么就可以解析出方法形参的某属性值,return “xiaoming”;
*/
return expression.getValue(context).toString();
}
}
DictServiceImpl に遅延二重削除アノテーションを追加
//更新:确保机制:实行双删
//重新赋值,只删除key = req.code+':'+#req.dictKey 的缓存项
@ClearAndReloadCache(spelName = "#req.code+':'+#req.dictKey")
@Caching(put ={@CachePut(key = "#req.code+':'+#req.dictKey",unless="#result == null")},
evict = { @CacheEvict(value = "dict", key = "#req.code"),
@CacheEvict(value = "dict", key = "'p:'+#req.parentId")})
@Override
public String doEdit(Dict req) {
QueryWrapper<Dict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Dict::getCode, req.getCode());
queryWrapper.lambda().eq(Dict::getDictKey, req.getDictKey());
queryWrapper.lambda().eq(Dict::getParentId, req.getParentId());
//修改时,不能将parentId code和key改为其他已经存在的code和key
queryWrapper.lambda().ne(Dict::getId, req.getId());
List<Dict> list = baseMapper.selectList(queryWrapper);
if (list.size() > 0){
return null;
}
baseMapper.updateById(req);
}
注: インターネット上の多くの場所で、コントローラーに記述する必要があると書かれています。そうしないと、説明できない問題が発生します (まだ遭遇していません)。
その他の学習:
1. アノテーションベースのサポート
Spring は、Spring Cache をサポートするためにいくつかのアノテーションを提供します。そのコアは主に@Cacheableと@CacheEvictです。Spring Cache は @Cacheable でマークされたメソッドの戻り結果を実行後にキャッシュしますが、 @CacheEvict でマークされたメソッドはメソッドの実行前または後に Spring Cache 内の特定の要素を削除します。以下では、Cache のアノテーション サポートに基づいて Spring が提供するいくつかのアノテーションを詳しく紹介します。
1.1 @キャッシュ可能
@Cacheable はメソッドまたはクラスでマークできます。
- メソッドにマークが付いている場合、そのメソッドがキャッシュをサポートしていることを示します。
- classにマークが付いている場合は、クラスのすべてのメソッドがキャッシュをサポートしていることを意味します。
キャッシュをサポートするメソッドの場合、Spring は呼び出し後にその戻り値をキャッシュし、次回同じパラメータでメソッドが実行されるときに、メソッドを再度実行することなく結果をキャッシュから直接取得できるようにします。Spring がメソッドの戻り値をキャッシュするときは、それをキーと値のペアとしてキャッシュします。値はメソッドの戻り値です。キーに関しては、Spring は 2 つの戦略 (デフォルト戦略とカスタム戦略) をサポートしています。後で説明します。キャッシュが有効なメソッドがオブジェクト内で呼び出された場合、キャッシュはトリガーされないことに注意してください。
@Cacheable では、値、キー、条件の 3 つの属性を指定できます。