How to get the value of a specific parameter in a jointpoint in an "Around" processing?

Asked

Viewed 85 times

1

I have the following method:

@RequestMapping(path = "/{tenant}/import", method = RequestMethod.POST, consumes = "application/json", produces = "text/plain; charset=UTF-8")
@Transactional
public ResponseEntity<String> requisicaoImportacao(@PathVariable("tenant") String tenant,
                                                       @RequestHeader(name = "authentication") String token,
                                                       HttpServletRequest req, HttpServletResponse resp) {

  ...

  // faz lógica de negócio

  ...
}

I need to make sure that for each tenant, i get a unique access. Only this also needs to be done in other methods. To avoid changing the business code, I decided to put in an aspect this processing.

I created the annotation TenantMutex to deal with it. The business code has been changed only by adding the notation:

@RequestMapping(path = "/{tenant}/import", method = RequestMethod.POST, consumes = "application/json", produces = "text/plain; charset=UTF-8")
@Transactional
@TenantMutex(0)
public ResponseEntity<String> requisicaoImportacao(@PathVariable("tenant") String tenant,
                                                       @RequestHeader(name = "authentication") String token,
                                                       HttpServletRequest req, HttpServletResponse resp) {

  ...

  // faz lógica de negócio

  ...
}

And the processing of this aspect was implemented as follows:

@Aspect
@Component
public class ConcurrencyAspect {

    private final HashMap<String, ReentrantLock> tenantLocks;

    public ConcurrencyAspect(@Autowired @Qualifier("tenantLocks") HashMap<String, ReentrantLock> tenantLocks) {
        this.tenantLocks = tenantLocks;
    }

    @Around(value = "@annotation(tenantMutex)")
    public Object tenantMutex(ProceedingJoinPoint jp, TenantMutex tenantMutex) throws Throwable {
        String tenant = (String) jp.getArgs()[tenantMutex.value()];
        if (tenant == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST);
        }
        tenant = tenant.toLowerCase();
        ReentrantLock l = tenantLocks.get(tenant);
        if (!l.tryLock(10, TimeUnit.SECONDS)) {
            throw new HttpServerErrorException(HttpStatus.SERVICE_UNAVAILABLE);
        }
        try {
            return jp.proceed();
        } finally {
            l.unlock();
        }
    }
}

I managed to get the value of tenant making the "gambiarra" to put, in the note TenantMutex, the position of the argument of tenant (in the case of this method, it was at position 0). However, this did not seem so correct.

Is there any more natural alternative to get the value of this parameter within the processing "around" the jointpoint? Preferably that it is somehow passed as an argument to the method that deals with the jointpoint?

  • I found something related to this: https://www.javainuse.com/spring/spring-boot-aop and also https://stackoverflow.com/q/5568617/4438007 ; maybe you can use and transform in a reply

  • Another interesting point: https://docs.spring.io/spring/docs/2.0.x/reference/aop.html#aop-pointcuts-designators

  • I was able to make an implementation based on this: https://stackoverflow.com/a/3567170/4438007

1 answer

1


At the end of the day, I used reflection proper.

I noted which parameter I would like to treat as @Tenant. To be able to identify (route AspectJ) which methods I would like you to have this, I detect this need when who is annotated is the method itself or even the class (if the class is so annotated, all its public methods should address this question of the @Tenant).

Roughly speaking, that’s what my code looks like:

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Aspect
@Component
public class TenantAspect {

    // minha classe proprietária descendente de org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
    private final TenantRoutingDataSource tenantDataSource;

    public TenantAspect(@Autowired TenantRoutingDataSource tenantDataSource) {
        this.tenantDataSource = tenantDataSource;
    }

    @Around(value = "@within(Tenant) || @annotation(Tenant)")
    public Object tenant(ProceedingJoinPoint jp) throws Throwable {
        try {
            String tenant = AnnotationUtils.getSingleParamWithAnnotation(jp, Tenant.class);
            if (tenant == null) {
                throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
            }
            tenant = tenant.toLowerCase();
            if (!tenantDataSource.isTenantValid(tenant)) {
                throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
            }
            TenantContextHolder.setTenantId(tenant);
            return jp.proceed();
        } finally {
            TenantContextHolder.clearTenantId();
        }
    }
}

Explaining:

  • the annotation @Order of Spring indicates the "relative position" of execution of annotation processors; a priori, all processors have the least relevance possible, but I needed the tenant was set before starting a transaction (indicated by @Transactional spring)
    then, for convenience’s sake, I placed my processor as having the highest precedence
  • the annotation @Aspect of Aspectj is to be treated by Aspectj precisely as an aspect processor, nothing secret here
  • @Component to allow Spring to make the dependency injections as it sees fit

So I have the class processed with @Tenant (@within(Tenant)) as well as noted methods (@annotation(Tenant)). As you can see, I extracted the magic of processing with reflection from that specific point to another class:

    @SuppressWarnings("unchecked")
    public static <T> T getSingleParamWithAnnotation(ProceedingJoinPoint jp, Class<?> annotation) {
        Signature sig = jp.getSignature();
        if (sig instanceof MethodSignature) {
            int idx = AnnotationUtils.getSingleParamIdxWithAnnotation((MethodSignature) sig, annotation);
            if (idx != -1) {
                return (T) jp.getArgs()[idx];
            }
        }
        return null;
    }

    public static int getSingleParamIdxWithAnnotation(MethodSignature methodSig, Class<?> annotation) {
        Annotation[][] annotatedParams = methodSig.getMethod().getParameterAnnotations();

        int s = annotatedParams.length;
        for (int i = 0; i < s; i++) {
            Annotation[] annotatedParam = annotatedParams[i];
            int si = annotatedParam.length;
            for (int j = 0; j < si; j++) {
                if (annotation.isInstance(annotatedParam[j])) {
                    return i;
                }
            }
        }
        return -1;
    }

    ...

Here, I check if the JointPoint really is a method call. A spring documentation in this regard the following:

In Spring AOP, a Join point Always represents a method Execution

That is, using aspect-oriented programming by Spring, you at all times will have an execution of methods such as JointPoint. This is indicated by AspectJ through the signing as an instance MethodSignature. This interface allows you to access the method that is on JointPoint, returning a good old java.lang.reflect.Method.

Thus, I need to know which annotations are linked to each parameter. I can know this through getParameterAnnotations(), that returns me a vector of vectors, relating to each parameter (first index) which annotations (vector associated with the index).

So, to know which is the first parameter annotated with the specific annotation, just ask in your annotations which is the instance of the annotation:

Annotation[][] annotatedParams = methodSig.getMethod().getParameterAnnotations();

int s = annotatedParams.length;
for (int i = 0; i < s; i++) {
    Annotation[] annotatedParam = annotatedParams[i];
    int si = annotatedParam.length;
    for (int j = 0; j < si; j++) {
        if (annotation.isInstance(annotatedParam[j])) {
            return i;
        }
    }
}
return -1;

Note that return -1 if the annotation cannot be found in the parameters.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.