【问题标题】: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 可以工作,但它是设计时问题的运行时解决方案。

标签: java class public


【解决方案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:是的,你是对的,这不应该仅仅出于模块化的目的,而只有在实际的安全概念需要它时才这样做。
    • stackoverflow.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] 将调用 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 TrunfioRonan 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);
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 2017-02-27
                • 2011-04-11
                • 2015-08-09
                • 1970-01-01
                • 2011-01-30
                • 1970-01-01
                • 2014-08-06
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多