Java - 如何限制特定方法的方法调用

时间:2015-03-17 15:35:59

标签: java methods restrict

我有一个特殊的要求,我需要确保只允许一个类中的特定方法从第二个类调用公共(非静态)方法。不能使用继承。

一种选择是使用StackTrace,如下所示:

ClassA.java

package org.rnd.stack;


public class ClassA {
    public void methodA() throws IllegalAccessException {
        Exception fake = new Exception("FAKE-IGNORE");
        StackTraceElement[] stack = fake.getStackTrace();
        StackTraceElement st = stack[1];
        if ("org.rnd.stack.ClassB".equals(st.getClassName())
                && "methodB".equals(st.getMethodName())) {
            System.out.println("You are allowed to call");
        } else {
            throw new IllegalAccessException("You are not allowed to call");
        }
    }
}

ClassB.java

package org.rnd.stack;

public class ClassB {
    public void methodB() throws IllegalAccessException {
        new ClassA().methodA();
    }

    public void illegalMethod() throws IllegalAccessException {
        new ClassA().methodA();
    }

    public static void main(String[] args) {
        try {
            new ClassB().methodB();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

现在上述解决方案工作正常,但由于代码审核中的质量控制,我需要提出另一个(或更确切地说)更好的解决方案。有没有更好的方法来实现这一目标?

4 个答案:

答案 0 :(得分:7)

正确的做法是重新审视您的要求。只能由某些其他代码路径调用的方法与public不兼容。一般的最佳做法是使用package-private来阻止外部调用者,并接受包中的任何代码调用该方法,但不会因为您或您的团队正在审核它而获胜。

方法可见性最终不是防止执行的安全解决方案;有人拥有你的.class文件并能够在机器上执行它们,他们可以做任何他们想要的事情。你不应该花太多时间试图锁定方法调用。相反,要清楚地记录方法的意图(例如" 帮助methodB()的功能,请不要在别处使用。")并相信与你共同发展的人他们正在做什么。你甚至可以给这个方法一个明确的名称,比如dangerousMethodBForInternalUseOnly(),如果你真的想要打败人们的话。

您可能也对dependency-injection感兴趣,Google I/O 2009 - Big Modular Java with Guice是一种使用类型系统来保护(不阻止)人们执行危险代码的设计模式。这里有一些关于Guice的讨论,Guice是一个流行的DI框架,它详细介绍了这个概念:


所有这些都说,作为一种学术练习,这里是将方法调用限制为固定数量的代码路径的一种选择 - 依赖于共享秘密。向锁定方法添加Object secret字段,如果传递的secret与硬编码值(private static final Object SECRET = new Object())不匹配,则会导致方法失败。然后,您可以使用其他机制仅将密码共享给您允许的代码路径(例如,在锁定类中使用静态初始化程序将其发布到您明确信任的类中)。

显然,这仍然是恶意开发人员可以解决的问题,并且它非常严重,但它会提供某种锁定行为,假设您可以信任您的锁定类无法更改在你不知情的情况下。

答案 1 :(得分:2)

改进方法的一种方法是,您不需要创建异常来获取堆栈跟踪,您可以使用线程方法。

StackTraceElement[] stack = Thread.currentThread().getStackTrace();

也许你想使用该类而不是手写包。例如:

if (ClassB.class.getName().equals(st.getClassName())
                && "methodB".equals(st.getMethodName())) {
    System.out.println("You are allowed to call");
} else {
    throw new IllegalAccessException("You are not allowed to call");
}

除此之外,我不知道如何在不改变逻辑或使用继承的情况下更好地完成它。

答案 2 :(得分:1)

  1. 将调用者作为参数传递,并检查调用者是否为instanceof所需的类 - 多线程解决方案,不能通过反射来绕过。
  2. 获取线程堆栈转储并检查顶部条目 - 奇怪,沉重但可能
  3. 创建代理 - 但这将是解决方案1的过度变更。

答案 3 :(得分:1)

您可以使用类Class方法getEnclosingMethod()来满足此要求。这是它的工作原理(docs here):

  

如果此Class对象表示方法中的本地或匿名类,则返回表示基础类的直接封闭方法的Method对象。

  1. 应更改methodA()的签名以接受Class对象作为参数。

    public void methodA(Class c) { }
    
  2. 来自ClassB的合法方法应该创建一个匿名类对象,并将其作为参数传递给methodA()

    public void methodB() throws IllegalAccessException, NoSuchMethodException {
        new ClassA().methodA(new Object(){}.getClass());
    }
    
  3. 然后methodA()应检查来自methodB()的类封闭方法是否确实ClassB

    public void methodA(Class c) throws IllegalAccessException, NoSuchMethodException {
        if (c.getEnclosingMethod().equals(ClassB.class.getMethod("methodB"))) {
            System.out.println("You are allowed to call");
        } else {
            throw new IllegalAccessException("You are not allowed to call");
        }
    }
    
  4. 缺点:

    • 每次调用methodB()时,都必须实例化一个新对象。根据您执行此操作的次数,这可能会变得昂贵。相反,您可以在methodB()内创建一个本地类,因此没有对象创建开销

      public void methodB() throws IllegalAccessException, NoSuchMethodException {
          class Local {};
          new ClassA().methodA(Local.class);
      }
      
    • 如果NoSuchMethodException名称发生变化,您需要处理methodB()并更改代码;

    • 有权访问代码的人仍然可以修改methodB()以将匿名对象类返回给另一个方法,并使用它从那里调用methodA()所以这不是一个完美的解决方案,但可能足以满足您的使用需求。