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

Java 如何防止从特定类调用公共方法,java,class,public,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 ex

我有一个要添加方法的现有类。但是我希望只从特定类的特定方法调用该方法。是否有任何方法可以阻止来自其他类/方法的调用

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


我所寻找的是一个更干净、更可取的解决方案,比如模式或其他什么。

正确使用受保护的
在java中实现这一点的标准方法是将类B和类a放在同一个包中(可能是当前应用程序的子包),并使用默认可见性

默认java可见性为“包私有”,这意味着包中的所有内容都可以看到您的方法,但包外的任何内容都不能访问它

另请参见:

在运行时确保的唯一方法是进行堆栈跟踪。即使它是私有的,您也可以通过反射访问该方法


一种更简单的方法是检查IDE中的用法。(如果没有通过反射调用)

,你可以考虑使用一个接口。如果要传入调用类,则可以确认该类属于适当的类型


或者,如果您使用的是Java,则可以使用“默认”或“包”级别的访问(例如,void方法()与public void方法()。这将允许包中的任何类调用您的方法,并且不需要将类传递给该方法。

老实说,您已经把自己画到了一个角落

如果类A和类B不相关,也不是同一个包的成员,那么可见性就不能解决问题。(即使是这样,反射也可以用来颠覆可见性规则。)

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

传递并检查
B.this
作为
A.method(…)
的额外参数没有帮助,因为其他类
C
可以传递
B
实例

这只剩下stacktrace方法1。。。或者放弃并依靠程序员的良好判断力2不调用他们不应该调用的方法


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


1-参见其他答案,了解使用注释、安全管理器等对应用程序程序员隐藏stacktrace内容的示例。但请注意,在引擎盖下,每个方法调用可能会增加数百甚至数千条指令的开销


2-不要低估程序员的良好判断力。大多数程序员在看到不调用某个方法的建议时,很可能会遵循该建议。

正确的方法是使用SecurityManager

定义所有要调用
a.method()
的代码必须拥有的权限,然后确保只有
B
a
拥有该权限(这也意味着没有类拥有
AllPermission

A
中,使用
System.getSecurityManager().checkPermission(新的BMethodPermission())
进行检查,在
B
中调用
AccessController.doPrivileged(…)
中的方法


当然,这需要安装安全管理器(并且它使用合适的策略)——如果不安装,则所有代码都是可信的,每个人都可以调用所有代码(如果需要,可以使用反射)。

您可以通过使用注释和反射来实现。我将报告一个类似的情况,即您可以让方法仅由extnal类中的特定方法调用的情况。假设必须通过调用其公共方法来“保护”的类是
调用的
,而
调用方
是启用了一个方法来调用
调用的
中的一个或多个方法的类。然后,您可以执行类似下面报告的操作

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
    }
   }
}
请注意,启用调用的方法使用
CanInvoke
注释进行注释

你要求的情况与此相似。注释不能调用公共方法的类/方法,然后仅当方法/类未注释时,才将
canExecute
变量设置为
true

可以使用类似的工具,并将其添加到构建过程中,以检查是否遵守了某些规则,如

<?xml version="1.0"?>
<macker>    
    <ruleset name="Simple example">
        <access-rule>
            <deny>
                <from class="**Print*" />
                <to class="java.**" />
            </deny>
        </access-rule>
    </ruleset>
</macker>

它不会阻止您编写错误的代码,但如果您使用Maven或其他构建系统,它可能会在构建过程中引发错误


这些工具在“类”级别而不是“方法”级别上工作,但我不认为阻止某个类只调用一个方法有什么意义…

我意识到您的用例状态是“特定类中特定的方法”,但我认为您无法在设计时可靠地解决这个问题(我想不出一个无论如何都必须强制执行的用例)

下面的示例创建了一个简单的设计时解决方案,用于将类的方法的访问限制到特定的类。但是,它可以轻松地扩展到多个允许的类

它是通过定义一个公共内部类和一个私有构造函数来实现的,该构造函数充当当前方法的键。在下面的示例中,类
Bar
有一个方法,该方法只能从
Foo
类的实例调用

Foo类: 分类栏: 我认为这对于反射之类的东西,甚至对于
FooPrivateKey.class.newInstance()
之类的东西,都是不安全的,但这至少会比简单的注释或文档更明显地警告程序员
<?xml version="1.0"?>
<macker>    
    <ruleset name="Simple example">
        <access-rule>
            <deny>
                <from class="**Print*" />
                <to class="java.**" />
            </deny>
        </access-rule>
    </ruleset>
</macker>
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.
    }
}
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;
}
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);
    }
}