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

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() 的代码都必须拥有的权限,然后确保只有BA 拥有该权限(这也意味着没有类拥有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 方法被一个类调用而不是 ShutdownHandlercheckPermission 将抛出一个IllegalStateException

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

getStackTrace()[0] 将调用 getStackTrace 本身。 getStackTrace()[1] 将调用 checkPermissiongetStackTrace()[2] 将调用 stopgetStackTrace()[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);
    

【讨论】:

以上是关于如何防止从特定类调用公共方法的主要内容,如果未能解决你的问题,请参考以下文章

iOS:如何定义公共方法?

为啥我不能在另一个类中调用公共方法?

asp.net中数据库连接的公共类的调用方法

如何在没有继承方法的情况下获取类的公共方法?

THINKPHP怎么在类中做一个公共的方法,让此类中得方法在运行时都先运行这个方法!

C++通过其派生类对象调用公共父类方法