如何防止从特定类调用公共方法
Posted
技术标签:
【中文标题】如何防止从特定类调用公共方法【英文标题】:How to prevent public methods from being called from specific classes 【发布时间】:2011-07-26 02:24:33 【问题描述】:我有一个现有的类,我想在其中添加一个方法。但我希望仅从特定类的特定方法调用该方法。有什么方法可以阻止来自其他类/方法的调用?
例如,我有一个现有的 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
,然后确认父方法?
我正在寻找的是一种更干净、更可取的解决方案,例如模式或其他东西。
【问题讨论】:
您可以尝试在 B 类中使用 method(thisobject) 并在 A 类中使用参数扩展该函数,让它检查 thisobject 是否为 B StackTrace 可以工作,但它是设计时问题的运行时解决方案。 【参考方案1】:说实话,你在这里把自己画到了一个角落。
如果类 A 和 B 不相关并且不是同一个包的成员,那么可见性将无法解决问题。 (即使是这样,反射也可以用来颠覆可见性规则。)
如果代码可以使用反射调用方法,静态代码分析将无法解决问题。
传递和检查B.this
作为A.method(...)
的额外参数并没有帮助,因为其他一些类C
可以传递B
实例。
这仅留下堆栈跟踪方法1...或放弃并依靠程序员的良好意识2不调用他们不应该调用的方法。
理想的解决方案是重新审视让您陷入困境的设计和/或编码决策。
1 - 有关使用注释、安全管理器等向应用程序程序员隐藏堆栈跟踪内容的示例,请参阅其他答案。但请注意,您可能会在 每个方法调用中添加数百甚至数千条指令开销。
2 - 不要低估程序员的良好意识。大多数程序员,当他们看到不调用某个方法的建议时,很可能会遵循该建议。
【讨论】:
【参考方案2】:正确的做法是使用 SecurityManager。
定义所有想要调用A.method()
的代码都必须拥有的权限,然后确保只有B
和A
拥有该权限(这也意味着没有类拥有AllPermission
)。
在A
中,您使用System.getSecurityManager().checkPermission(new BMethodPermission())
进行检查,在B
中,您调用AccessController.doPrivileged(...)
内部的方法。
当然,这需要安装一个安全管理器(并且它使用合适的策略)——如果没有,所有代码都是可信的,每个人都可以调用一切(如果需要,使用反射)。
【讨论】:
你可能还想要一个doPrivileged
。真是一团糟。
@Tom:我想我有 doPrivileged
在里面。
哦,原来如此。仍然一团糟(并且需要修改public
方法,以及奇怪的权限分配)。
@Tom:是的,你是对的,这不应该仅仅出于模块化的目的,而只有在实际的安全概念需要它时才这样做。
***.com/q/50407042/14955 有一个后续问题,人们礼貌地要求提供代码示例。【参考方案3】:
您可以考虑使用接口。如果您传入调用类,您可以确认该类是适当的类型。
或者,如果您使用 Java,您可以使用“默认”或“包”级别访问(例如 void method() 与 public void method())。这将允许您的方法被包内的任何类调用,并且不需要您将类传递给方法。
【讨论】:
我也在考虑一些类似界面的东西【参考方案4】:在运行时确定的唯一方法是获取堆栈跟踪。即使它是私有的,您也可以通过反射访问该方法。
执行此操作的更简单方法是检查 IDE 中的使用情况。 (前提是它不是通过反射调用的)
【讨论】:
+1 - 您还可以定义自定义 Annotation 来表达限制,并编写静态分析工具来检查限制是否被违反。 (当然,反射可以颠覆这一点。)【参考方案5】:正如其他人所提到的,使用堆栈跟踪是实现您正在寻找的功能的一种方式。通常,如果需要“阻止”来自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
只检查类名。添加检查方法名称的功能只需要进行一些修改,所以我将把它留作练习。
【讨论】:
【参考方案6】:正确使用protected
【讨论】:
如果我使方法受保护,那么我不能使用它,因为 B 类不是 A 的子类。A 类也是最终的。 @Swaranga Sarma -protected
扩展到同一个包中的类。
你可以这样设计.. 否则使用堆栈跟踪的黑客攻击很差
如果您可以稍微重构一下您的包结构,您可以使用“package-private”默认可见性。看我的回答。
@birryree...我的 B 类 aleady 存在并且它在不同的包中【参考方案7】:
在 java 中执行此操作的标准方法是将 Class B 和 Class A 放在同一个包中(可能是您当前应用程序的子包)并使用默认可见性。
默认的 java 可见性是“package-private”,这意味着该包中的所有内容都可以看到您的方法,但该包之外的任何内容都无法访问它。
另请参阅:Is there a way to simulate the C++ 'friend' concept in Java?
【讨论】:
【参考方案8】:您可以通过使用注释和反射来做到这一点。我将报告一个类似的情况,即您可以让方法仅由外部类的特定方法调用的情况。假设必须通过对其公共方法的任何调用来“保护”的类是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
。
【讨论】:
【参考方案9】:您可以使用Macker 之类的工具并将其添加到您的构建过程中以检查是否遵守某些规则,例如
<?xml version="1.0"?>
<macker>
<ruleset name="Simple example">
<access-rule>
<deny>
<from class="**Print*" />
<to class="java.**" />
</deny>
</access-rule>
</ruleset>
</macker>
它不会阻止您编写错误的代码,但如果您使用 Maven 或其他构建系统,它可能会在您的构建过程中引发错误。
此工具在“类”级别而不是“方法”级别工作,但我看不出阻止某个类仅调用一个方法的意义...
【讨论】:
【参考方案10】:我意识到您的用例状态是“特定 方法 在特定类中”,但我认为您无法可靠地解决这个问题设计时间(我想不出无论如何都必须强制执行的用例)。
以下示例创建了一个简单的设计时解决方案,用于限制类方法对特定类的访问。但是,它可以轻松扩展到多个允许的类。
它是通过定义一个带有私有构造函数的公共内部类来实现的,该构造函数充当手头方法的键。在下面的示例中,Bar
类有一个只能从 Foo
类的实例调用的方法。
Foo 类:
public class Foo
public Foo()
Bar bar = new Bar();
bar.method(new FooPrivateKey());
public class FooPrivateKey
private FooPrivateKey()
类栏:
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.
我认为这对于反射之类的东西甚至FooPrivateKey.class.newInstance()
之类的东西无论如何都不安全,但这至少会比简单的注释或文档更突兀地警告程序员,而你没有必须研究更复杂的事情,例如 Roberto Trunfio 和 Ronan Quillevere 等人的建议(这也是完全可行的答案,在我看来对于大多数情况来说太复杂了)。
我希望这对您的用例来说已经足够了。
【讨论】:
【参考方案11】:假设您只需要将此限制应用于项目中的类,静态分析可以为您工作 - 例如 ArchUnit 测试:
package net.openid.conformance.archunit;
import com.google.gson.JsonElement;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.AccessTarget;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import net.openid.conformance.testmodule.OIDFJSON;
import org.junit.Test;
import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.*;
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
public class PreventGetAs
@Test
public void doNotCallJsonElementGetAs()
JavaClasses importedClasses = new ClassFileImporter().importPackages("net.openid.conformance");
JavaClasses allExceptOIDFJSON = importedClasses.that(DescribedPredicate.not(nameContaining("OIDFJSON")));
ArchRule rule = noClasses().should().callMethodWhere(
target(nameMatching("getAs[^J].*")) // ignores getAsJsonObject/getAsJsonPrimitive/etc which are fine
.and(target(owner(assignableTo(JsonElement.class)))
)).because("the getAs methods perform implicit conversions that might not be desirable - use OIDFJSON wrapper instead");
rule.check(allExceptOIDFJSON);
【讨论】:
以上是关于如何防止从特定类调用公共方法的主要内容,如果未能解决你的问题,请参考以下文章