La anotación personalizada de Spring utiliza la inyección de aop para implementar la función de eliminación doble retrasada

1.: Introduzca la dependencia de Redis y la anotación personalizada @DataChange e implemente el controlador:

 <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>

Archivo de configuraciónMyCacheConfig

@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();
            }
        };

    }
}

Resuelve el problema de que el objeto devuelto se almacenará en caché cuando se use la anotación @Cacheable y el valor almacenado en caché predeterminado es binario .

2. Uso de anotaciones personalizadas

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
    //普通的操作说明
    String name() default "";

    //spel表达式的操作说明
    String spelName() default "";
}

Nota: (Debe poder utilizar expresiones ortográficas)

3.ClearAndReloadCacheAspect retrasó la doble eliminación de aspectos


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;//返回业务代码的值
    }
}

Método SpelUtil utilizado

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();
    }
}

Agregue una anotación de doble eliminación retrasada en 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);
    }

Nota: Muchos lugares en Internet dicen que es necesario escribirlo en el Controlador; de lo contrario, habrá problemas inexplicables (aún no lo he encontrado)

Otros aprendizajes:

1. Soporte basado en anotaciones

Spring nos proporciona varias anotaciones para admitir Spring Cache. Su núcleo es principalmente @Cacheable y @CacheEvict . Spring Cache almacenará en caché los resultados devueltos de los métodos marcados con @Cacheable después de la ejecución, mientras que los métodos marcados con @CacheEvict eliminarán ciertos elementos en Spring Cache antes o después de que se ejecute el método. A continuación, presentaremos en detalle varias anotaciones proporcionadas por Spring según el soporte de anotaciones para Cache.

1.1 @Cacheable

@Cacheable se puede marcar en un método o clase.

  • Cuando se marca en un método , indica que el método admite el almacenamiento en caché
  • Cuando está marcado en una clase , significa que todos los métodos de la clase admiten el almacenamiento en caché.

Para un método que admite el almacenamiento en caché, Spring almacenará en caché su valor de retorno después de llamarlo para garantizar que la próxima vez que se ejecute el método con los mismos parámetros, los resultados se puedan obtener directamente del caché sin la necesidad de ejecutar el método nuevamente. Cuando Spring almacena en caché el valor de retorno de un método, lo almacena en caché como un par clave-valor. El valor es el resultado de retorno del método. En cuanto a la clave, Spring admite dos estrategias, la estrategia predeterminada y la estrategia personalizada , que explicarse más adelante. Cabe señalar que el almacenamiento en caché no se activará cuando se llame a un método habilitado para caché dentro del objeto .
@Cacheable puede especificar tres atributos: valor, clave y condición.

Supongo que te gusta

Origin blog.csdn.net/qq_40453972/article/details/129802046
Recomendado
Clasificación