2

I have an issue with an Aspect I coded:

@Aspect
@Component
public class MyAudit {

@Pointcut("@annotation(requestMapping)")
public void controller(RequestMapping requestMapping) {
}

@Around("controller(requestMapping)")
public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {        

...

In one path of the code, I am doing a lookup for authorization and I need to return info to the user WITHOUT executing the method. Basically, I need to shortcut out of the flow.

I have code like this:

    log.warn("Not permitted per policy. Returning without executing: " + authError);      
    Map<String,String> responseBody = new HashMap<>();
    responseBody.put("path",request.getContextPath());
    responseBody.put("message",authError);
    return new ResponseEntity<>(responseBody,HttpStatus.OK);

The problem is that when I return ResponseEntity, it falls right back into my 'around' method. Not sure why it's executing again.

Someone on another thread mentioned that I shouldn't be annotating this as @Component, but I tried removing that and when I do, the @Around method doesn't execute at all.

Can anyone point out what I'm doing wrong?

UPDATE:

Here's the complete class with some code related to business-specific items removed.

import com.ge.aviation.paasport.model.Audit;
import com.ge.aviation.paasport.repository.AuditRepository;
import com.ge.aviation.paasport.util.SecurityUtils;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.lang.annotation.Annotation;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import org.apache.http.HttpResponse;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
public class MyAudit {

    @Pointcut("@annotation(requestMapping)")
    public void controller(RequestMapping requestMapping) {
    }

    @Around("controller(requestMapping)")
    public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {        

        String authError = null;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        String decision = SecurityUtils.evaluatePolicy(request); 
        if(!(decision.toUpperCase().equals(SecurityUtils.PERMIT))) {
           authError = decision + " : Request not permittted per policy.";
           log.warn("Not permitted per policy. Returning without executing: " + authError);      
           Map<String,String> responseBody = new HashMap<>();
           responseBody.put("path",request.getContextPath());
           responseBody.put("message",authError);
           return new ResponseEntity<>(responseBody,HttpStatus.OK);
        }
        return pjp.proceed();
  }
}

When it executes return new ResponseEntity<>(responseBody,HttpStatus.OK); in the @Around advice, it re-enters the around method a 2nd time.

I set a breakpoint on the actual method that would be executed in the normal flow and it's never hit.

Seems it's this ma.invoke method (return ma.invoke(obj, args);) which is hit when returning the ResponseEntity and it launches right back into the around method again.

 package java.lang.reflect;

 ...
 ...

 public final class Method extends Executable {

 ...
 ...

 @CallerSensitive
  public Object invoke(Object obj, Object... args)
      throws IllegalAccessException, IllegalArgumentException,
         InvocationTargetException
  {
      if (!override) {
          if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
              Class<?> caller = Reflection.getCallerClass();
              checkAccess(caller, clazz, obj, modifiers);
          }
      }
      MethodAccessor ma = methodAccessor;             // read volatile
      if (ma == null) {
          ma = acquireMethodAccessor();
      }
      return ma.invoke(obj, args);
  }
}
DimaSan
  • 12,264
  • 11
  • 65
  • 75
Mike
  • 763
  • 2
  • 12
  • 25
  • Please edit your post including what you do exactly in your around advice. Best would be if you would include the whole aspect. Also, are you using Spring AOP, or AspectJ (is there a compile time weaving step in your build or a load-time weaver enabled at runtime)? – Nándor Előd Fekete Dec 01 '16 at 01:53
  • `@annotation(...)` pointcut by itself matches both method execution an method call join points, so if you're really using AspectJ that might be a reason for the double method call. Spring AOP only supports method execution join points, but it's still worth adding an `&& execution(...)` to your pointcut expression. That's why I asked if you're actually using AspectJ or Spring AOP, it's important to know when trying to figure out your problem. So here's the question again: is there a compile time weaving step in your build or a load-time weaver enabled at runtime? – Nándor Előd Fekete Dec 01 '16 at 15:28
  • Also see this [answer](http://stackoverflow.com/a/1606780/2699901) for some details on the difference between AspectJ and Spring AOP. – Nándor Előd Fekete Dec 01 '16 at 15:30
  • Sorry. I am using Spring AOP. I'm having a look at the link you just sent. – Mike Dec 01 '16 at 22:47
  • Sorry. I was wrong. I'm using aspectj import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; – Mike Dec 01 '16 at 23:26
  • I've also simplified my annotations so I'm using only 1. It still executes 2x when returning a ResponseEntity: @Around("@annotation(requestMapping)") public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable { System.out.println("AROUND ADVICE - ENTERING EXECUTING!"); – Mike Dec 02 '16 at 00:06
  • Your import statements tell nothing about whether you're actually AspectJ or Spring AOP, as Spring AOP supports a subset of AspectJ semantics with all the limitations outlined in the answer I linked earlier. You can also tell whether it's AspectJ or Spring AOP from the stack when you're breaking on the advice body, because with Spring AOP you'll have a proxy near the top of the stack, while with AspectJ you'll have synthetic methods weaved into your own classes by the AspectJ weaver. – Nándor Előd Fekete Dec 02 '16 at 02:44

1 Answers1

1

Thanks for the help. I figured it out...

The first time it entered my advice method:

pjp =
    (org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint)
    execution(List com.myCompany.controller.ApplicationController.getApplications())

2nd time entering the method (after I returned my 401 response):

pjp =
    (org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint)
    execution(ResponseEntity org.springframework.boot.autoconfigure.web.BasicErrorController.error(HttpServletRequest))

I added this for my aspect:

@Around("@annotation(requestMapping) && execution( * com.myCompany.controller..*.*(..))")
public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {        

and it only executed once.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Mike
  • 763
  • 2
  • 12
  • 25
  • So you figured it out. Your pointcut was too broad, intercepting just everything annotated by `@RequestMapping`. As documented, method [BasicErrorController.error](http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/BasicErrorController.html#error-javax.servlet.http.HttpServletRequest-) also bears the same annotation. Maybe you want to accept your own answer so as to close the question. – kriegaex Dec 03 '16 at 10:59