【问题标题】:Check in Spring AOP if method is called by another annotated method如果方法被另一个带注释的方法调用,则检查 Spring AOP
【发布时间】:2021-02-03 12:38:52
【问题描述】:

我在我的 Spring boot 项目中使用了在注释类中的每个公共方法上触发的方面:

@Aspect
@Component
public class DeletedAwareAspect {
    @Before("@within(com.example.DeleteAware)")
    public void aroundExecution(JoinPoint pjp) throws Throwable {
        //... some logic before
    }

    @After("@within(com.example.DeleteAware)")
    public void cleanUp(JoinPoint pjp) throws Throwable {
       //... some logic after
    }
}

该方面的用法如下:

@DeleteAware
@Service
public class MyService {
    public void foo() {}
}
@DeleteAware
@Service
public class MyAnotherService {
    @Autowired
    private MyService service;

    public void anotherFoo() {}

    public void VERY_IMPORTANT_METHOD() {
        service.foo();
    }
}

MyService.foo() 和 MyAnotherService.anotherFoo() 按预期工作。但是这里有问题 - 如果由方面包装的方法被另一个方面方法(如 VERY_IMPORTANT_METHOD())调用,我不想触发方面两次,但只触发一次。 如何从 Aspect 检查方法是否在另一个切面方法内部被调用?

【问题讨论】:

  • 只是一个想法,我目前正在使用手机,因此无法尝试:joinpoint.getStaticPart() 包含静态信息。也许可以检查连接点是否来自同一个带注释的类。我希望你明白我的意思。为了获得更好的性能,您还可以将方法签名中的 JoinPoint 替换为 StaticPart 类型。应该类似于Joinpoint.StaticPart
  • 如果不检查调用堆栈,就无法识别调用者方法。解决此问题的另一种方法是将VERY_IMPORTANT_METHOD() 从建议方法的范围中排除。例如 @within(com.example.DeleteAware) && !@annotation(ExcludeMethod) ,其中 ExcludeMethod 将是方法级别的注释。将需要修改代码来识别此类方法。另一种方法是使用 threadlocal 来标记它已经被建议并在执行逻辑之前检查建议中的相同内容。
  • 您是否考虑过切换到原生 AspectJ 并使用 cflow()cflowbelow()? P.S.:看起来你想做类似于@Transactional 所做的事情。由于您没有解释方面的作用,因此无法判断您是否可以使用该注释。我也无法具体建议如何在没有本机 AspectJ 的情况下重新实现您的方面,以便不删除资源两次,无论是使用提到的 ThreadLocal 还是通过其他方式。因此,如果您想进一步讨论此问题,请提供MCVE
  • @kriegaex,是的,我想实现非常相似的功能,例如“事务”(需要传播)。更具体地说,我需要在“之前”方面打开会话上的 JPA 过滤器,并在“之后”部分将其关闭。如果我使用该注释将服务注入另一个服务,它就不起作用,因为一旦内部服务结束,它会关闭我的过滤器,然后在外部服务过滤器已经关闭并且我的功能不起作用。
  • 是的,在这种情况下,您需要手动记账。问题:最外层的“之前”和对应的“之后”建议之间的所有内容是否都在一个线程中?这将大大简化事情。您对可能方法的选择很大程度上取决于您对这个问题的回答。

标签: spring-boot aop aspectj spring-aop


【解决方案1】:

就像我说的,如果您要切换到本机 AspectJ,您可以使用 percflow() 方面实例化和/或 cflow() 切入点,但我不会在这里详细解释,因为您正在寻找 Spring AOP 解决方案。就像 R.G 说的那样,它实际上非常简单:在方面嵌套级别使用线程本地计数器,并且仅在计数器为零时才执行某些操作(例如,删除后通知中的某些内容)。

@Aspect
@Component
public class DeletedAwareAspect {
  private ThreadLocal<Integer> nestingLevel = ThreadLocal.withInitial(() -> 0);

  @Before("@within(com.example.DeleteAware)")
  public void aroundExecution(JoinPoint pjp) {
    int level = nestingLevel.get() + 1;
    nestingLevel.set(level);
    System.out.println("BEFORE " + pjp + " -> " + level);
  }

  @After("@within(com.example.DeleteAware)")
  public void cleanUp(JoinPoint pjp) {
    int level = nestingLevel.get() - 1;
    nestingLevel.set(level);
    System.out.println("AFTER " + pjp + " -> " + level);
    if (level == 0)
      System.out.println("Deleting something");
  }
}

【讨论】:

    猜你喜欢
    • 2018-02-23
    • 2012-11-13
    • 1970-01-01
    • 2011-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多