【问题标题】:Spring AOP @Around advice firing twice when returning ResponseEntitySpring AOP @Around 建议在返回 ResponseEntity 时触发两次
【发布时间】:2016-12-01 01:49:12
【问题描述】:

我编码的方面有问题:

@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 {        

...

在代码的一个路径中,我正在查找授权,我需要在不执行该方法的情况下将信息返回给用户。基本上,我需要从流程中走捷径。

我有这样的代码:

    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);

问题是,当我返回 ResponseEntity 时,它会立即回到我的“环绕”方法中。不知道为什么会再次执行。

另一个线程上的某人提到我不应该将其注释为@Component,但我尝试删除它,当我这样做时,@Around 方法根本不会执行。

谁能指出我做错了什么?

更新:

这是一个完整的类,其中删除了一些与特定业务项目相关的代码。

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();
  }
}

当它在@Around 建议中执行return new ResponseEntity&lt;&gt;(responseBody,HttpStatus.OK); 时,它会第二次重新进入around 方法。

我在将在正常流程中执行的实际方法上设置了一个断点,并且它永远不会被命中。

似乎是这个 ma.invoke 方法 (return ma.invoke(obj, args);) 在返回 ResponseEntity 时被命中,它再次启动回到 around 方法。

 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);
  }
}

【问题讨论】:

  • 请编辑您的帖子,包括您在周围建议中所做的确切内容。最好是包含整个方面。另外,您使用的是 Spring AOP 还是 AspectJ(在您的构建中是否有编译时编织步骤或在运行时启用了加载时编织器)?
  • @annotation(...) 切入点本身匹配方法执行和方法调用连接点,因此如果您真的使用 AspectJ,这可能是双重方法调用的原因。 Spring AOP 仅支持方法执行连接点,但仍然值得在您的切入点表达式中添加 &amp;&amp; execution(...)。这就是为什么我问你是否真的在使用 AspectJ 或 Spring AOP,在试图找出你的问题时知道这一点很重要。那么问题又来了:在您的构建中是否有编译时编织步骤或在运行时启用了加载时编织器?
  • 有关 AspectJ 和 Spring AOP 之间区别的一些详细信息,请参见 answer
  • 对不起。我正在使用 Spring AOP。我正在查看您刚刚发送的链接。
  • 对不起。我错了。我正在使用 aspectj import org.aspectj.lang.JoinPoint;导入 org.aspectj.lang.ProceedingJoinPoint;导入 org.aspectj.lang.annotation.Around;导入 org.aspectj.lang.annotation.Aspect;导入 org.aspectj.lang.annotation.Pointcut;

标签: spring spring-boot aop spring-aop aspect


【解决方案1】:

感谢您的帮助。我想通了……

第一次进入我的advice方法:

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

第二次进入方法(在我返回 401 响应之后):

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

我为我的方面添加了这个:

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

它只执行一次。

【讨论】:

  • 所以你想通了。您的切入点太宽泛,只截取了 @RequestMapping 注释的所有内容。如文档所述,方法BasicErrorController.error 也带有相同的注释。也许您想接受自己的答案以结束问题。
猜你喜欢
  • 2022-08-20
  • 1970-01-01
  • 2011-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-23
  • 1970-01-01
相关资源
最近更新 更多