Spring 사용자 정의 주석은 Aop 주입을 사용하여 지연된 이중 삭제 기능을 구현합니다.

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 주석을 사용할 때 반환된 객체가 캐시되고 , 기본 캐시 값이 바이너리인 문제를 해결합니다 .

2. 맞춤 주석 사용

import java.lang.annotation.*;

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

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

참고: (철자 표현을 사용할 수 있어야 합니다)

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은 기본 전략과 사용자 정의 전략의 두 가지 전략을 지원 합니다 . 나중에 설명하겠습니다. 캐시가 활성화된 메소드가 객체 내부에서 호출되면 캐싱이 트리거되지 않는다는 점에 유의해야 합니다 .
@Cacheable은 값, 키, 조건이라는 세 가지 속성을 지정할 수 있습니다.

추천

출처blog.csdn.net/qq_40453972/article/details/129802046