This is the 24th day of my participation in the August Text Challenge.More challenges in August

Before reading this chapter, it is recommended to have a certain foundation of Spring AOP. If you need it, you can go to see my article on AOP foundation

Spring AOP is quick to get started

The project structure

mapper
	|- Mapper
		|- xxxMapper
		|- ...
aspect
	|- MapperAspect
Copy the code

The mapper layer

Define a standard Mapper layer full of xxMapper. Normally, we will use MyBaits to access the database. MyBaits will provide a parent mapper class with most of the normal SQL operations

Since the plan on the project side is that there is another module that encapsulates all operations to the database, I will use HTTP protocol to read and write data to that module, so I need to encapsulate a common Mapper myself

The parent class Mapper

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

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

The Mapper class uses generics on its declaration, representing the entity of the operation, all subclasses of RcsDto

Mapper defines a protected CLZ variable that represents the class of the concrete class that the generic corresponds to when Mapper is inherited

Note that the constructor for Mapper, with two lines of code, gets the exact type of the generic on the class definition declared as a subclass of Mapper

Subclasses Mapper

A regular subclass, inheriting directly from its parent class without having to define additional methods, can own all the methods of its parent class, and this subclass, the entity of the operation, is declared when it inherits, as shown in the following code: AreaInfoDto

@Component
public class AreaMapper extends Mapper<AreaInfoDto> {}
// dtoClz = areainfodto.class
Copy the code

If, we need to implement additional methods, as follows, the code is very easy to write

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

The aspect aspect

So let’s start with the Mapper section code

@Aspect
@Component
public class HttpResultAspect {

  // Update, update cache, HTTP request to pass data
  @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;
  }

  // Delete to clear the corresponding cache record
  @Before("execution (* com.rlzz.r9.mapper.*Mapper.delete(..) )"
  public void beforeDelete(JoinPoint joinPoint) {}// Query a single file to check whether it exists in the cache
  @Around("execution (* com.rlzz.r9.mapper.*Mapper.getById(..) )"
  public <D extends RcsDto> D aroundGetById(ProceedingJoinPoint joinPoint) {}// Query all the data in the cache
  @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);
    returndtos; }}Copy the code

Like the first @around, we intercept all save(), update() in xxMapper, we pass Object obj = joinPoint.proceed(); Executes the target method, retrieves the return value, checks the return value and adds it to the Cache.

For example, with the last @around, we intercepted xxmapper.findall (), but did not actually execute it. Instead, we chose to pull all data of the corresponding type directly from the cache. This involves a method called AspectMothod.

The following code

public class AspectMothod {
  // Use JoinPoint to obtain the value of dtoClz, a custom field in mapper
  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) {	// If it was obtained before, return it directly
      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<>();
}
Copy the code

Here used a very interesting detail point, reflection, different partners to see this is not far from proficient Java advanced features reflection

Recall that when we declare Mapper, we define a generic class variable, class

dtoClz, in the parent class, but when we create a subclass instance, we assign it to the generic type passed in at runtime

Now, in the AOP aspect, we need to know which specific xxxMapper we are cutting and what instances of this mapper correspond to. Because we can use JoinPoint.gettarget () to see which instance we cut

And then, by reflection, get its superclass, and then get a field in that superclass (1).

Call the reflection method to get the value of this field (2)

Cache the resulting results in map for next quick access (3)