我有一个现有的类,我想添加一个方法。但我希望只从特定类的特定方法调用该方法。有什么方法可以阻止来自其他类/方法的调用吗?
例如,我有一个现有的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
}
}
这样做的一种方法是在StackTrace
内获取method()
,然后确认父方法?
我正在寻找的是一种更清洁,更可行的解决方案,如模式或其他东西。
答案 0 :(得分:9)
说实话,你已经把自己画在了一个角落里。
如果A类和B类不相关且不是同一个包的成员,那么可见性将无法解决问题。 (即使它确实如此,也可以使用反射来破坏可见性规则。)
如果代码可以使用反射来调用方法,静态代码分析将无法解决问题。
将B.this
作为A.method(...)
的额外参数传递和检查无济于事,因为其他一些类C
可以传递B
个实例。
这只留下堆栈跟踪方法 1 ...或放弃并依赖程序员 2 的良好意识,不要调用他们不应该的方法。
理想的解决方案是重新审视让您陷入困境的设计和/或编码决策。
1 - 有关使用注释,安全管理器等的示例,请参阅其他答案,以隐藏应用程序编程人员的堆栈跟踪内容。但请注意,在引擎盖下,每个方法调用可能会增加数百个,可能是成千上万的指令。
2 - 不要低估程序员的良好意识。大多数程序员在看到建议不要使用某种方法时,可能会遵循这个建议。
答案 1 :(得分:7)
执行此操作的正确方法是SecurityManager。
定义一个权限,所有想要调用A.method()
的代码都必须拥有该权限,然后确保只有B
和A
具有该权限(这也意味着没有类具有{ {1}})。
在AllPermission
中,您使用A
进行检查,然后在System.getSecurityManager().checkPermission(new BMethodPermission())
中调用B
内的方法。
当然,这需要安装一个安全管理器(并且它使用合适的策略) - 如果不是,所有代码都是可信的,每个人都可以调用所有内容(如果需要,使用Reflection)。
答案 2 :(得分:6)
您可以考虑使用界面。如果您正在传入调用类,则可以确认该类是否为适当的类型。
或者,如果您使用的是Java,则可以使用“默认”或“包”级访问(例如,void method()与public void method())。这将允许您的方法被包内的任何类调用,并且不要求您将类传递给方法。
答案 3 :(得分:2)
正确使用protected
答案 4 :(得分:2)
在运行时检查确定的唯一方法是进行堆栈跟踪。即使它是私有的,您也可以通过反射访问该方法。
更简单的方法是检查IDE中的用法。 (如果没有通过反射调用)
答案 5 :(得分:2)
正如其他人所提到的,使用堆栈跟踪是实现您正在寻找的功能的一种方法。通常,如果需要从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 :(得分:1)
在java中执行此操作的标准方法是将B类和A类放在同一个包中(可能是当前应用程序的子包)并使用默认可见性。
默认的java可见性是“package-private”,这意味着该包中的所有内容都可以看到您的方法,但该包之外的任何内容都无法访问它。
参见:
Is there a way to simulate the C++ 'friend' concept in Java?
答案 7 :(得分:0)
您可以使用注释和反射来完成。我将报告一个类似的情况,即您可以通过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
注释进行注释。
您要求的案件类似。您注释了无法调用公共方法的类/方法,然后仅在方法/类未注释时才设置为true
canExecute
变量。
答案 8 :(得分:0)
您可以使用像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或其他构建系统,则可能会在构建过程中引发错误。
这个工具在“类”级别工作而不是在“方法”级别工作,但我没有看到阻止从某个类只调用一个方法的点......
答案 9 :(得分:0)
我意识到您的用例在特定类&#39>中列出了特定的 方法 ,但我没有&#39;我认为你可以在设计时可靠地解决这个问题(我无法想到一个必须强制实施的用例)。
以下示例创建了一个简单的设计时解决方案,用于限制类的访问权限。特定类的方法。但是,它可以很容易地扩展到多个允许的类。
通过使用私有构造函数定义公共内部类来实现,该私有构造函数充当手头方法的键。在以下示例中,类Bar
具有只应从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这样的人所建议的更复杂的事情(这些也是完全可行的答案,对于我认为的大多数情况来说太复杂了)。
我希望这足以满足您的使用案例。
答案 10 :(得分:0)
假设您只需要将此限制应用于项目中的类,静态分析可能适合您 - 例如 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);
}
}