项目中 AOP 的实际使用

789 阅读3分钟

这是我参与8月更文挑战的第 24 天,活动详情查看:8月更文挑战

阅读本章之前,建议有一定的 Spring AOP 基础,有需要的小伙伴可以去看看我的写的 AOP 基础的文章

Spring AOP 快速上手

项目结构

mapper
	|- Mapper
		|- xxxMapper
		|- ...
aspect
	|- MapperAspect

mapper 层

定义一个标准的 mapper 层,里面全是 xxMapper,正常的情况下,我们会使用 MyBaits 访问数据库,MyBaits 会提供一个具备大部分常规 sql 操作的父类 Mapper

由于项目这边的规划是,有另一个模块封装所有对数据库的操作,我这里通过 http协议 去那个模块读写数据,需要自己封装一个公共的 Mapper

父类 Mapper

public class Mapper<D extends RcsDto> {
  protected Class<D> dtoClz;

  public Mapper() {
    ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
    dtoClz = (Class<D>) pt.getActualTypeArguments()[0];
  }
  public List<D> findAll() {
    return HttpResultUtil.rGetList(dtoClz);
  }
  public Result delete(String id) {
    return HttpResultUtil.rDeleteById(id, dtoClz);
  }
}

Mapper 类的声明上使用了泛型,表示操作的实体,全是 RcsDto 的子类

Mapper 中定义了一个受保护的 clz 变量,表示的是继承 Mapper 的时候泛型对应的具体类的 class

注意看 Mapper 的构造器,通过两行代码,获取到声明为 Mapper 的子类的类定义上的泛型的具体类型(建议背诵),大概是这么个效果

子类 Mapper

一个常规的子类,不需要额外定义方法,直接继承至父类,就能拥有其父类的全部方法,而且这个子类,操作的具体实体,在继承时声明,如下代码,其声明操作的实体就是 AreaInfoDto

@Component
public class AreaMapper extends Mapper<AreaInfoDto> {}
// 此时,Mapper 中的 dtoClz = AreaInfoDto.class

如果,我们需要追加实现其他的方法,如下,代码编写非常简单轻松

@Component
public class TaskMapper extends Mapper<TaskInfoDto> {
  public void deleteAll(String rid) {
    HttpResultUtil.rDeleteByRcsId(rid, TaskInfoDto.class);
  }
}

aspect 切面

先上 Mapper 的切面代码

@Aspect
@Component
public class HttpResultAspect {

  //更新,更新缓存,http请求传递数据
  @Around("execution (* com.rlzz.r9.mapper.*Mapper.update(..)) "
          + "|| execution (* com.rlzz.r9.mapper.*Mapper.save(..))")
  public <D extends RcsDto> D afterSaveOrUpdate(ProceedingJoinPoint joinPoint) {
    try {
      D dto = (D) joinPoint.getArgs()[0];
      Object obj = joinPoint.proceed();
      if (!ObjectUtils.isEmpty(obj)) {
        dto = (D) obj;
        DtoCache.getInstance().add(dto);
      }
      return dto;
    } catch (Throwable e) {
      LogUtil.exception(e);
    }
    return null;
  }

  // 删除,清空对应的缓存记录
  @Before("execution (* com.rlzz.r9.mapper.*Mapper.delete(..))")
  public void beforeDelete(JoinPoint joinPoint) {
  }

  //查询单个,判断缓存中是否存在
  @Around("execution (* com.rlzz.r9.mapper.*Mapper.getById(..))")
  public <D extends RcsDto> D aroundGetById(ProceedingJoinPoint joinPoint) {
  }

  //查询全部,获取返回的结果,直接覆盖缓存中的数据
  @Around("execution (* com.rlzz.r9.mapper.*Mapper.findAll(..))")
  public <D extends RcsDto> List<D> aroundFindAll(ProceedingJoinPoint joinPoint) {
    Class dtoClz = AspectMothod.getDtoClass(joinPoint);
    List<D> dtos = DtoCache.getInstance().getList(dtoClz);
    //		LogUtil.info(dtos);
    return dtos;
  }
}

像第一个 @Around,我们拦截了 xxMapper 里所有的 save(), update(),我们通过 Object obj = joinPoint.proceed(); 的方式,执行了目标方法,并拿到了返回值,然后对返回值做检查并添加到 Cache 中(当然,又是自己造的轮子)

比如最后一个 @Around,我们虽然拦截了 xxMapper.findAll(),但实际上并没去去执行,而且选择去缓存中直接拉去对应类型的全部数据,这里涉及到一个方法 AspectMothod.getDtoClass(joinPoint)

代码如下

public class AspectMothod {
  // 通过 JoinPoint 获取mapper中自定义的字段,dtoClz 的值
  static <D extends RcsDto> Class<D> getDtoClass(JoinPoint joinPoint) {
    //		JsonUtil.error(joinPoint.getSignature().getDeclaringType(), joinPoint.getSignature().getName(), joinPoint.getArgs());
    Object obj = joinPoint.getTarget();
    if (map.get(obj.getClass()) != null) {	// 如果之前获取过,直接返回
      return map.get(obj.getClass());
    }
    Class<D> clz = null;
    Field field = null;
    try {
      field = obj.getClass().getSuperclass().getDeclaredField("dtoClz");	// (1)
      field.setAccessible(true);
      clz = (Class<D>) field.get(obj);	// (2)
    } catch (Exception e) {
      LogUtil.exception(e);
    }
    map.put(obj.getClass(), clz);	// (3)
    return clz;
  }
  private static Map<Class, Class> map = new HashMap<>();
}

这里用到了一个非常有意思的细节点,反射,不同的小伙伴来看看这个就离精通不远了 Java 高级特性の反射

再回想一下,我们在声明 Mapper 的时候,是在里面定义了一个泛型的class 变量, Class<D> dtoClz,虽然是定义在父类里,但是,我们在创建子类实例的时候,又会将他赋值为运行时传入的泛型类型

现在,我们在 AOP 切面中,需要知道我们切的是哪一个具体的 xxxMapper,这个 mapper 对应的实例又是什么。因为我们在切面中,用 joinPoint.getTarget() 就能得知我们具体切了哪一个实例

然后,通过反射的方式,拿到他的父类,在去拿父类里的某一个字段 (1)

调用反射方法,获取这个字段的值 (2)

将得到的结果缓存到 map 中,供下次快速访问 (3)