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

时间:2011-03-30 13:15:52

标签: java class public

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

例如,我有一个现有的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(),然后确认父方法?

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

11 个答案:

答案 0 :(得分:9)

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

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

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

B.this作为A.method(...)的额外参数传递和检查无济于事,因为其他一些类C可以传递B个实例。

这只留下堆栈跟踪方法 1 ...或放弃并依赖程序员 2 的良好意识,不要调用他们不应该的方法。


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


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

2 - 不要低估程序员的良好意识。大多数程序员在看到建议不要使用某种方法时,可能会遵循这个建议。

答案 1 :(得分:7)

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

定义一个权限,所有想要调用A.method()的代码都必须拥有该权限,然后确保只有BA具有该权限(这也意味着没有类具有{ {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类的实例调用的方法。

Class Foo:

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

    public class FooPrivateKey
    {
        private FooPrivateKey()
        {   }
    }  
}

Class Bar:

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 TrunfioRonan 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);
    }
}