问题 在Java中对方法体施加约束或限制


上下文(编辑)

有些澄清是需要的,所以我会试着总结影响这个问题的因素。

  • 该项目的目标是为程序员提供某种功能,最有可能是以库的形式(我猜是带有类文件的JAR)。

  • 要使用所述功能,程序员必须遵守这些约束 必须 (应该)满意。否则它将无法按预期运行(就像来自的锁一样) java.util.concurrent,必须在适当的时间和地点获得/释放)。

  • 此代码不是使用它的应用程序的入口点(, 没有 main)。

  • API中公开的操作数量有限(且很小)。

例子:

  1. 想想一个小游戏,几乎所有东西都是由已经实现的类实现和管理的。程序员唯一要做的就是编写一个方法或其中几个描述角色将要做什么的方法(走路,改变方向,停止,检查对象)。我想确保他们的方法(可能标有注释?) walk, 要么 changeDirection,或计算 diff = desiredValue - x,而不是说,写入某个文件,或打开套接字连接。

  2. 想想一个交易经理。该库提供管理器,以及事务的一些常量属性(它们的隔离级别,超时,......)。现在,程序员想要进行交易并使用这个经理。我想确保他们只是 readwritecommit, 要么 rollback 在一些资源上,经理知道。我不希望他们这样做 launchRocket 在交易过程中,如果经理不控制任何火箭发射。

问题

我想在方法(或方法组)的主体上施加一些不变量/限制/约束,稍后由其他程序员在其他一些包/位置中实现。说,我给他们一些东西:

public abstract class ToBeExtended {
    // some private stuff they should not modify
    // ...
    public abstract SomeReturnType safeMethod();
}

为了本项目的目的,方法体满足一些不变量是很重要的(可能是必要的)。或者更确切地说,此方法实现使用的命令集是有限的。这些约束的例子:

  • 此方法不得执行任何I / O.
  • 此方法不得实例化任何未知(有潜在危险)对象。
  • ...

换一种方式:

  • 此方法可以调用已知(特定)类的方法。
  • 这个方法可以执行一些 基本 指令(数学,分配局部变量, ifs,循环...)。

我一直在浏览Annotations,似乎没有什么可以接近的。
我到目前为止的选择:

  1. 定义一些注释, @SafeAnnotation,并将其应用于方法,与实施者定义合同,他将遵守规则,否则系统将发生故障。

  2. 定义一个 Enum 允许的操作。而不是暴露允许的方法,只暴露一个方法,接受这些枚举对象的列表(或类似于 控制流程图?)并执行它,让我控制可以做什么。

例:

public enum AllowedOperations { OP1, OP2 }

public class TheOneKnown {
    public void executeMyStuff (List<AllowedOperations> ops) {
        // ...
    }
}

我的问题

是否有语言中的任何功能,例如注释,反射或其他,允许我检查(在编译时或运行时)方法是否有效(,满足我的约束)?
或者更确切地说,有没有办法强制它只调用一组有限的其他方法?

如果不是(我认为不是),第二种方法是否合适?
适用于直观,精心设计和/或良好实践。

更新(进度)

看了一些相关的问题后,我也在考虑(作为第三种选择,也许)按照接受的答案给出的步骤 这个问题。虽然,这可能需要对架构进行一些重新思考。

使用注释来施加限制的整个想法似乎需要实现我自己的注释处理器。如果这是真的,我不妨考虑一个特定于域的小语言,以便程序员使用这些有限的操作,然后将代码转换为Java。这样,我也可以控制指定的内容。


7951
2018-04-11 21:21


起源

也许你编写了自己的注释处理器,但也就是说。 - Louis Wasserman
这就是我所怀疑的。为了让程序员仍然编写代码,而不是通过对象列表进行编码,我们可能不得不编写一些预处理器(用于Java或小域特定语言)。 - afsantos
听起来像Google在App Engine上做的事情 - developers.google.com/appengine/docs/java/jrewhitelist。我怕我不知道他们是怎么做到的!也许这个问题也有帮助 - stackoverflow.com/questions/1715036/...。 - Paul Grime
实际上,这听起来类似于定义可以做什么的白名单。我不知道Google App Engine上的这些限制。不同之处在于,在这种情况下,白名单仅适用于该特定方法的每个实现,而不是整个应用程序。不过,这些原则仍然适用。 - afsantos
如果您有合同要执行并仍想为其他程序员定义API,那么您的问题是否不适合通过使用接口来分离角色?您可以提供所需的实现,将其转换为API,强制执行合同,并且仍然允许最终用户在合同约束内提供他们想要的行为。您的问题有点像界面的定义。 - scottb


答案:


我认为是方向 在这个问题上 很好。

  • 使用具体的 ClassLoader 加载课程。要注意,它们是一种有趣的马类型,通常会发生类本身由父类加载器加载。可能你想要某种东西 URLClassLoader的,并且父类加载器将被设置为Root类加载器但是这还不够。
  • 使用 threads 避免无限循环(而不是实现 Runnable 比延伸 Thread,就像那里) - 如果你不担心它,这可能是不必要的。
  • 使用SecurityManager来避免 java.io 操作

除了以上,我建议2个选项:

给出方法a 调节器,它将包含它可以调用的函数

例如:

public void foo(Controller ctrl) {
}

public class Controller {
   public boolean commit();
   public boolean rollback();
}

这可以为用户提供句柄,允许哪些操作。

用一个 Intent类似命令模式

在Android中,系统的组件非常封闭。他们不能直接相互沟通,他们只能发动事件,“发生这种情况”或“我想这样做”。

这样,可用命令的集合不受限制。通常,如果方法只做小业务逻辑,那就足够了。


3
2018-04-17 07:49



我会接受这个答案,因为它与我在最近的更新中所描述的非常接近,并且还描述了我打算尝试的内容。我认为关于控制器和意图的建议可能会有所帮助。我不知道Android使用的Intent机制。 - afsantos


看看java策略文件。我没有使用过它们,而且我不确定它们是否完全适合你的问题,但是有些人可能会对文档进行深入研究。这里有几个可能有帮助的SO问题

限制Java中的文件访问

什么是简单的Java安全策略来限制文件写入单个目录?

这里有一些关于策略文件的文档。

http://docs.oracle.com/javase/6/docs/technotes/guides/security/PolicyFiles.html


6
2018-04-11 21:36





您可以使用自定义类加载器限制不受信任的代码使用的类:

public class SafeClassLoader extends ClassLoader {

    Set<String> safe = new HashSet<>();

    {
        String[] s = {
            "java.lang.Object",
            "java.lang.String",
            "java.lang.Integer"
        };
        safe.addAll(Arrays.asList(s));
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        if (safe.contains(name)) {
            return super.loadClass(name, resolve);
        } else {
            throw new ClassNotFoundException(name);
        }
    }
}

public class Sandboxer {
    public static void main(String[] args) throws Exception {
        File f = new File("bin/");
        URL[] urls = {f.toURI().toURL()};
        ClassLoader loader = new URLClassLoader(urls, new SafeClassLoader());
        Class<?> good = loader.loadClass("tools.sandbox.Good");
        System.out.println(good.newInstance().toString());
        Class<?> evil = loader.loadClass("tools.sandbox.Evil");
        System.out.println(evil.newInstance().toString());
    }
}

public class Good {
    @Override
    public String toString() {
        return "I am good";
    }
}

public class Evil {
    @Override
    public String toString() {
        new Thread().start();
        return "I am evil.";
    }
}

运行此将导致

I am good
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Thread
    at tools.sandbox.Evil.toString(Evil.java:7)
    at tools.sandbox.Sandboxer.main(Sandboxer.java:18)
Caused by: java.lang.ClassNotFoundException: java.lang.Thread
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    ... 2 more

当然,这假设要关注您的白名单。它也无法阻止拒绝服务等内容

while (true) {}

要么

new long[1000000000];

4
2018-04-11 23:41



这似乎是一个有趣的替代方案,但是,根据我的理解,ClassLoader不适用于整个应用程序吗?或者,假设程序员应该扩展我的课程。例如,这个ClassLoader只能用于我的超类的最终方法吗? - afsantos
不,几个类加载器可以共存。在上面的示例应用程序中,Sandboxer是从引导类加载器加载的。它不是(也可能不是)从SafeClassLoader加载的。在您的情况下,您将使用特殊的类加载器加载具有不可信代码的子类,并将SafeClassLoader中的超类列入白名单,从引导类加载器授予不受信任的代码访问该特定类的权限。 - meriton


另一种选择是使用en嵌入式脚本解释器,例如groovy(http://groovy.codehaus.org/Embedding+Groovy并使用预执行验证在运行时评估第三方方法内容。

优点是您将能够仅限制对将为脚本执行绑定的变量的访问权限。

您也可以编写自己的验证dsl并将其应用于执行脚本的方法,例如使用自定义注释。


2
2018-04-17 06:41





有几个 按合同设计 可用于Java的库,但我不能特别推荐它。 Java参数验证 似乎是一个轻量级的解决方案,但同样,我没有第一手经验。


0
2018-04-11 21:25



我是合同设计的忠实粉丝,但是,就目前版本的要求而言,这些约束应该如此 强制执行,而不仅仅是 要求。在合同设计中,人们会说“如果你想要我的服务,你应该满足这些先决条件,否则我不能给你任何保证”。这种情况更像是“如果你想要我的服务,你 必须 满足这些条件。没有办法“。如果没有更合适的话,它仍然可以作为一种选择。 - afsantos