如何防止从特定类调用公共方法

我有一个现有的类,我想添加一个方法。 但我希望只从特定类的特定方法调用该方法。 有什么方法可以阻止来自其他类/方法的调用吗?

例如,我有一个现有的A类

public final class A { //other stuff available for all classes/methods //I want to add a method that does its job only if called from a specific method of a class, for example: public void method() { //proceed if called from Class B.anotherMethod() else throw Exception } } 

这样做的一种方法是在method()获取StackTrace ,然后确认父方法?

我正在寻找的是一种更清洁,更可行的解决方案,如模式或其他东西。

说实话,你已经在这里画了一个角落。

如果A类和B类不相关而不是同一个包的成员,那么可见性将无法解决问题。 (即使它确实如此,也可以使用reflection来破坏可见性规则。)

如果代码可以使用reflection来调用方法,则静态代码分析将无法解决问题。

传递和检查B.this作为A.method(...)的额外参数没有帮助,因为其他一些C类可以传递B实例。

这只留下了栈跟踪方法1 …或者放弃并依赖程序员2的良好意识而不是调用他们不应该的方法。


理想的解决方案是重新审视让您陷入困境的设计和/或编码决策。


1 – 有关使用注释,安全管理器等的示例,请参阅其他答案,以隐藏应用程序编程人员的堆栈跟踪内容。 但请注意,在引擎盖下, 每个方法调用可能会增加数百个,可能是数千个指令。

2 – 不要低估程序员的良好意识。 大多数程序员在看到不打电话给某些方法的建议时,可能会遵循这个建议。

执行此操作的正确方法是SecurityManager。

定义一个所有想要调用A.method()代码必须拥有的权限,然后确保只有BA具有该权限(这也意味着没有类具有AllPermission )。

A ,使用System.getSecurityManager().checkPermission(new BMethodPermission()) ,在B调用AccessController.doPrivileged(...)

当然,这需要安装一个安全管理器(并且它使用合适的策略) – 如果不是,则所有代码都是可信的,每个人都可以调用所有内容(如果需要,使用Reflection)。

您可以考虑使用界面。 如果您传入调用类,则可以确认该类是否为适当的类型。

或者,如果您使用的是Java,则可以使用“默认”或“包”级别访问(例如,void method()与public void method())。 这将允许您的方法由包内的任何类调用,并且不要求您将类传递给方法。

正确使用protected

在运行时检查确定的唯一方法是进行堆栈跟踪。 即使它是私有的,您也可以通过reflection访问该方法。

更简单的方法是检查IDE中的用法。 (如果没有通过reflection调用)

正如其他人所提到的,使用堆栈跟踪是实现您正在寻找的function的一种方法。 通常,如果需要从public方法“阻止”呼叫者,则可能是设计不良的迹象。 根据经验,使用尽可能限制范围的访问修饰符。 但是,使方法包私有或protected并不总是一种选择。 有时,人们可能希望将一些类组合在一个单独的包中。 在这种情况下,默认(包私有)访问限制性太强,通常对子类没有意义,因此protected也没有用。

如果需要限制调用某些类,可以创建一个方法,如:

 public static void checkPermission(Class... expectedCallerClasses) { StackTraceElement callerTrace = Thread.currentThread().getStackTrace()[3]; for (Class expectedClass : expectedCallerClasses) { if (callerTrace.getClassName().equals(expectedClass.getName())) { return; } } throw new RuntimeException("Bad caller."); } 

使用它非常简单:只需指定哪些类可以调用该方法。 例如,

 public void stop() { checkPermission(ShutdownHandler.class); running = false; } 

因此,如果stop方法被ShutdownHandler 以外的类调用, checkPermission将抛出IllegalStateException

您可能想知道为什么checkPermission被硬编码为使用堆栈跟踪的第四个元素。 这是因为Thread#getStackTrace()使最近调用的方法成为第一个元素。 所以,

  • getStackTrace()[0]将调用getStackTrace本身。
  • getStackTrace()[1]将调用checkPermission
  • getStackTrace()[2]将是stop的调用。
  • getStackTrace()[3]将是调用stop的方法。 这就是我们感兴趣的。

您提到要从特定的类方法调用方法,但checkPermission仅检查类名。 添加检查方法名称的function只需要进行一些修改,因此我将把它作为练习。

在java中执行此操作的标准方法是将Class B和Class A放在同一个包中(可能是当前应用程序的子包)并使用默认可见性。

默认的Java可见性是“package-private”,这意味着该包中的所有内容都可以看到您的方法,但该包之外的任何内容都无法访问它。

也可以看看:
有没有办法在Java中模拟C ++的“朋友”概念?

您可以使用注释和reflection来完成。 我将报告一个类似的情况,即您可以通过extenal类中的特定方法调用该方法的情况。 假设必须通过对其公共方法的任何调用“保护”的类是Invoked ,而Invoker是一个类,它具有一个允许从Invoked调用一个或多个方法的方法。 然后,您可以执行以下报告中的操作。

 public class Invoked{ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public static @interface CanInvoke{} public void methodToBeInvoked() { boolean canExecute=false; try { //get the caller class StackTraceElement element = (new Throwable()).getStackTrace()[1]; String className = element.getClassName(); Class callerClass = Class.forName(className); //check if caller method is annotated for (Method m : callerClass.getDeclaredMethods()) { if (m.getName().equals(methodName)) { if(Objects.nonNull(m.getAnnotation(EnabledToMakeOperationRemoved.class))){ canExecute = true; break; } } } } catch (SecurityException | ClassNotFoundException ex e) { //In my case does nothing } if(canExecute){ //do something } else{ //throw exception } } } 

Invoker类是

 public class Invoker{ private Invoked i; @Invoked.CanInvoke public void methodInvoker(){ i.methodToBeInvoked(); } } 

请注意,启用调用的方法使用CanInvoke批注进行批注。

您请求的案例类似。 您注释了无法调用公共方法的类/方法,然后仅在未注释方法/类时将canExecute变量设置为true

您可以使用像Macker这样的工具并将其添加到您的构建过程中,以检查一些规则是否受到尊重,例如

            

它不会阻止您编写错误的代码,但如果您使用Maven或其他构建系统,则可能会在构建过程中引发错误。

这个工具在“类”级别工作,而不是在“方法”级别,但我没有看到阻止从某个类只调用一个方法的点…

我意识到你的用例陈述了“ 特定 类中特定 方法 ”,但我认为你不能在设计时可靠地解决这个问题(而且我不能想到一个必须强制实施的用例)。

以下示例创建了一个简单的设计时解决方案,用于限制类方法对特定类的访问。 但是,它可以很容易地扩展到多个允许的类。

它是通过使用私有构造函数定义公共内部类来实现的,该私有构造函数充当手头方法的关键。 在下面的示例中,类Bar有一个只应从Foo类的实例调用的方法。

Foo类:

 public class Foo { public Foo() { Bar bar = new Bar(); bar.method(new FooPrivateKey()); } public class FooPrivateKey { private FooPrivateKey() { } } } 

class级栏:

 public class Bar { public Bar() { } public void method(FooPrivateKey fooPrivateKey) { if(fooPrivateKey == null) { throw new IllegalArgumentException("This method should only be called from the Foo class.");} //Do originally intended work. } } 

我不认为这对于reflection甚至像FooPrivateKey.class.newInstance()类的东西都是安全的,但这至少会警告程序员比简单的注释或文档更加突兀,而你不要我必须研究像Roberto Trunfio和Ronan Quillevere这样的人提出的更复杂的事情(这些都是完全可行的答案,在我看来,对于大多数情况来说太复杂了)。

我希望这对你的用例来说已经足够了。