面向切面编程三两事——通过redis缓存双删讲解AOP实际使用

什么是AOP?

AOP即我们常说的面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术!SpringAOP是Spring提供的一个标准易用的AOP框架,通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

为什么用AOP?

1、功能增强,在程序运行期间,在不修改源码的情况下对方法进行功能增强;
2、便于维护,将冗余且必须执行的代码放到AOP中执行,减少重复代码,提高开发效率,并且便于维护;
3、降低耦合,对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性;

怎么用AOP?

AOP使用方法即:设置切入点 + 切面逻辑实现;
切入点: 创建切入点注解类,并在需要执行切面操作的方法前加上对应切入点注解;
切面逻辑实现: 在切面实现类中根据业务情况在对应的通知方法中实现业务逻辑,切面通知方法主要有前置通知,后置通知,异常通知,环绕通知等,一般都是采用环绕通知实现对业务逻辑的实现!
1、引入依赖

<!-- aop -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、创建注解
新增注解类,可以根据业务情况设计注解填充参数!这边设计了两个字段,一个是redis键值前缀,一个是键值对组成字段!通过键值对前缀加上键值组成字段的值组成redis缓存的key值,从而对缓存进行操作!

/**
 * redis数据双删,实现数据一致性
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoDoubleDelRedisData {
    /******************redis键值为前缀加键值组成部分*******************/
    /**
     * redis 键值前缀
     */
    String[] prefixs();

    /**
     * redis 键值组成字段
     */
    String[] keyFields() default {};
}

3、完成切面逻辑
创建切面实现类,用于进入切面后的业务实现!通过@pointcut注解定位切入点,在@Around中实现进入切面后需要实现的功能(一般采用环绕通知,也可以根据实际情况使用用前置通知@Before或者后置通知@After等通知方式实现切面逻辑)!这边是在切入点前执行缓存操作,然后在方法执行后再进行一次缓存删除,从而达到缓存数据一致性。

@Aspect
@Component
public class AutoDoubleDelRedisDataAspect {
    //切入点
    @Pointcut("@annotation(com.example.redisdemo.aop.annotation.AutoDoubleDelRedisData)")
    public void doPointcut() {
    }

    /**
     * 环绕通知
     */
    @Around("doPointcut()")
    public Object doAroundAdvice(ProceedingJoinPoint point) throws Throwable {
        System.out.println("----------- 环绕通知 -----------");
        System.out.println("切入点目标方法名:" + point.getSignature().getName());
        //获取入参
        Object[] objs = point.getArgs();
        Object paramObject = null;
        if (objs.length > 0) {
            paramObject = objs[0];
        }
        //通过自定义注释获取自定义注解的方法对象
        MethodSignature methodSignature = (MethodSignature)point.getSignature();
        Method targetMethod = methodSignature.getMethod();//方法对象
        AutoDoubleDelRedisData annotation = targetMethod.getAnnotation(AutoDoubleDelRedisData.class);
        String[]  prefixs    = annotation.prefixs();
        String[] keyFields = annotation.keyFields();

        System.out.println("方法执行前:" + LocalDateTime.now());
        delRedisCache(paramObject,prefixs,keyFields);
        Object proceed = point.proceed();
        System.out.println("方法执行后:" + LocalDateTime.now());
        //可以设置延迟删除
        delRedisCache(paramObject,prefixs,keyFields);
        return proceed;
    }

    /**
     * 删除缓存信息
     * zlx
     * 16:29 2022/6/18
     * @param paramObject   入参参数
     * @param prefixs        键值前缀
     * @param keyFields     键值对饮id
     * @return void
     **/
    private void delRedisCache(Object paramObject,String[] prefixs,String[] keyFields) {
        for (int i = 0; i < prefixs.length; i++) {
            String redisKey = prefixs[i];
            String keyField = keyFields[i];
            if (paramObject.getClass() == String.class) {
                if (keyField != null && StrUtil.equals(keyField,"self")) {
                    redisKey = redisKey + paramObject;
                } else if (keyField != null && keyField.contains(";")) {
                    redisKey = buildRedisKey(redisKey,keyField,paramObject);
                }
            } else {
                if (StrUtil.isNotBlank(keyField)) {
                    redisKey = buildRedisKey(redisKey,keyField,paramObject);
                }
            }
            RedisUtils.scanDelKeys(redisKey);
        }

    }

    private String buildRedisKey(String redisKey, String keyField, Object paramObject) {
        String[] keys = keyField.split(";");
        for (int j = 0; j < keys.length; j++) {
            String key = keys[j];
            redisKey = redisKey + ReflectUtil.getFieldValue(paramObject, key);
            if (j == keys.length - 1) {
                redisKey = redisKey + "*";
            } else {
                redisKey = redisKey + ":";
            }
        }
        return redisKey;
    }

4、根据实际业务需求添加切面注解,如修改和删除信息
在需要进行切面的方法前加上@AutoDoubleDelRedisData(prefixs = “RECORD:”,keyFields = “recordId”)注解即可。

    /**
     * 根据记录id删除记录
     * zlx
     * 10:09 2022/6/18
     * @param recordId 记录id
     * @return boolean
     **/
    @Override
    @AutoDoubleDelRedisData(prefixs = "RECORD:",keyFields = "self")
    public boolean delRecord(String recordId) {
        return this.removeById(recordId);
    }

    /**
     * 修改记录内容
     * zlx
     * 10:06 2022/6/18
     * @param record    修改记录内容
     * @return com.example.redisdemo.entity.Record
     **/
    @Override
    @AutoDoubleDelRedisData(prefixs = "RECORD:",keyFields = "recordId")
    public Record putRecord(Record record) {
        record.setUpdateTime(LocalDateTime.now());
        if (this.updateById(record)) {
            return record;
        }
        return null;
    }

这样我们就实现了通过aop切面自动删除redis缓存的操作!可以通过http文件进行测试!当我们对记录进行修改或者删除操作时会进入切面实现类中,自动删除对应的缓存信息!
面向切面编程三两事——通过redis缓存双删讲解AOP实际使用_第1张图片

demo代码地址: https://gitee.com/zlx041192/redis-double-deletion-demo

你可能感兴趣的:(redis,缓存,java)