Java 如何防止从特定类调用公共方法
我有一个要添加方法的现有类。但是我希望只从特定类的特定方法调用该方法。是否有任何方法可以阻止来自其他类/方法的调用 例如,我有一个现有的A类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
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);
}
}