仅在调用者为特定类型时才允许执行方法

问题描述

我们有一个XRepository,它扩展了JpaRepository。自从最近删除X实体以来,我们特别创建了一个XDeletionService,其中包含很多...东西:-)并使用XRepository

现在,我们有一个有趣的想法,禁止在delete中执行任何XRepository方法,除非从XDeletionService内部调用这些方法。例如。如果同事从XRepository.delete(..)内部错误地直接致电TheirService打电话,将会抛出异常。

对于这个想法,我们仍然找不到合适的解决方案。到目前为止,我们要做的是创建一个切入点表达式的方面,该切入点表达式与存储库的delete方法相匹配。这方面通过查询堆栈跟踪引发了异常,例如:

boolean calledFromXDeletionService = Arrays.stream(Thread.currentThread().getStackTrace())
        .anyMatch(stackTraceElement ->
            XDeletionService.class.getName().equals(stackTraceElement.getClassName())); 
if (!calledFromXDeletionService)
   throw new ....

这似乎很难看。您是否有更好的主意如何实现此“功能”?

解决方法

我建议从Spring AOP切换到AspectJ,可以在使用Spring或完全不使用Spring的情况下使用它。我将发布一个独立的Java示例,但Spring手册还将说明如何配置AspectJ LTW (load-time weaving) for Spring

示例类+驱动程序应用程序

package de.scrum_master.app;

public class NormalType {
  public void callTargetMethod(Application application) {
    System.out.println("Normal caller");
    application.doSomething();
  }
}
package de.scrum_master.app;

public class SpecialType {
  public void callTargetMethod(Application application) {
    System.out.println("Special caller");
    application.doSomething();
  }
}
package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    Application application = new Application();
    application.callTargetMethod(application);
    callTargetMethodStatic(application);
    new NormalType().callTargetMethod(application);
    new SpecialType().callTargetMethod(application);
  }

  public void callTargetMethod(Application application) {
    System.out.println("Normal caller");
    application.doSomething();
  }

  public static void callTargetMethodStatic(Application application) {
    System.out.println("Static caller");
    application.doSomething();
  }

  public void doSomething() {
    System.out.println("Doing something");
  }
}

期望是,在运行小驱动程序应用程序时,实际上只会拦截从实例方法Application.doSomething()中发出的对SpecialType.callTargetMethod(..)的调用,而不是其他类的实例方法的调用,也不会来自静态方法的调用(与Spring AOP相比,可以在AspectJ中进行拦截)。

解决方案是使用call()切入点,该切入点与execution()相对,并且在Spring AOP中不可用。它拦截了调用者类内部的方法调用,而不是被调用者中的相应执行。这就是我们想要的,因为这样我们就可以使用this()来确定或缩小呼叫者类别。

仅对于call()this()(呼叫者)和target()(被呼叫者)的值之间存在差异。对于execution(),这两个值是相同的,这就是为什么Spring AOP不能在没有使用堆栈跟踪检查或更优雅,更有效地使用Java 9+中的Stack Walking API(https://www.baeldung.com/java-9-stackwalking-api)的情况下用于此目的的原因。

方面:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
  @Before(
    "call(void de.scrum_master.app.Application.doSomething()) && " +
    "this(de.scrum_master.app.SpecialType)"
  )
  public void myAdvice(JoinPoint joinPoint) {
    System.out.println(joinPoint);
  }
}

控制台日志:

Normal caller
Doing something
Static caller
Doing something
Normal caller
Doing something
Special caller
call(void de.scrum_master.app.Application.doSomething())
Doing something

如果您还想记录调用者实例,则可以像这样修改方面,将其绑定到通知方法参数:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import de.scrum_master.app.SpecialType;

@Aspect
public class MyAspect {
  @Before(
    "call(void de.scrum_master.app.Application.doSomething()) && " +
    "this(specialType)"
  )
  public void myAdvice(JoinPoint joinPoint,SpecialType specialType) {
    System.out.println(joinPoint + " -> " + specialType);
  }
}

控制台日志将为:

Normal caller
Doing something
Static caller
Doing something
Normal caller
Doing something
Special caller
call(void de.scrum_master.app.Application.doSomething()) -> de.scrum_master.app.SpecialType@402a079c
Doing something

更新:您可能还想尝试在建议中添加JoinPoint.EnclosingStaticPart enclosingStaticPart参数,然后打印和/或检查它。它可以帮助您找到有关call()切入点的调用者的更多信息,而不必诉诸堆栈跟踪或堆栈漫游API。

,

TheirService应该使用没有delete方法的接口,这被称为Interface Segregation Principle

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...