如何防止从特定类调用公共方法
我有一个现有的类,我想添加一个方法。 但我希望只从特定类的特定方法调用该方法。 有什么方法可以阻止来自其他类/方法的调用吗?
例如,我有一个现有的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()
代码必须拥有的权限,然后确保只有B
和A
具有该权限(这也意味着没有类具有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这样的人提出的更复杂的事情(这些都是完全可行的答案,在我看来,对于大多数情况来说太复杂了)。
我希望这对你的用例来说已经足够了。