我如何限制我的客户使用 class 中的选定方法?

How can i restrict my clients with selected methods from the class?

假设我有 1 个完整的 class,其中包含大约 20 种提供不同功能的方法。

现在我们有多个客户端使用此 class,但我们希望他们的访问权限受到限制。

例如-

客户端 1 - 可以访问 method1/m3/m5/m7/m9/m11

客户端 2 - 可以访问 method2/m4/m6/m8/m10/m12

有什么方法可以限制这种访问吗?

我想到的一个解决方案:

创建 2 个新的 classes 扩展 Parent class 并覆盖不可访问的方法并从中抛出异常。 但是如果第三个客户有不同的要求,我们必须为他们创建新的子class。

还有其他方法吗?

您可以创建一个只为 Client1 定义方法的 Interface1 和一个只为 Client2 定义方法的 Interface2。然后,您的 class 实现了 Interface1Interface2.

当您声明 Client1 时,您可以执行以下操作:Interface1 client1。 使用这种方法,client1 只能访问此接口的方法。

希望对您有所帮助。

您应该创建一个包含所有方法的超级 class,然后在从先前定义的超级 class 扩展的相应子 class 中提供客户端特定的实现。

如果有所有客户端通用实现的方法,将它们的实现留给超级 class。

Create 2 new classes extending Parent class and override methods which are not accessible and throw Exception from them. But then if 3rd client with different requirement, we have to create new subclass for them.

这是一个糟糕的解决方案,因为它违反了 Polymorphism and the Liskov Substitution Principle。这种方式会让你的代码不那么清晰。

首先,你应该想想你的class,你确定它没有被方法重载吗?您确定所有这些方法都与一个抽象相关吗?也许,将方法分离到不同的抽象和 classes?

是有意义的

如果 class 中存在这些方法是有意义的,那么您应该对不同的客户端使用不同的 interfaces。比如可以为每个client做两个接口

interface InterfaceForClient1 {
  public void m1();
  public void m3();
  public void m5();
  public void m7();
  public void m9();
  public void m11();
}

interface InterfaceForClient2 {
  public void m2();
  public void m4();
  public void m6();
  public void m8();
  public void m10();
  public void m12();
}

并在您的 class

中实施它们
class MyClass implements InterfaceForClient1, InterfaceForClient2 {
}

之后,客户端必须使用这些接口而不是 class 的具体实现来实现自己的逻辑。

其他答案已经提供了惯用的方法。另一个想法是 dynamic proxy 用访问检查装饰 API。

本质上,您生成了一个代理 API,它对方法调用进行额外检查以实现某种形式的访问控制。

示例实现:

package com.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@FunctionalInterface
public interface ACL<P, Q> {

    boolean allowed(P accessor, Q target, Method method, Object[] args);

    class ACLException extends RuntimeException {
        ACLException(String message) {
            super(message);
        }
    }

    @SuppressWarnings("unchecked")
    default Q protect(P accessor, Q delegate, Class<Q> dType) {
        if (!dType.isInterface()) {
            throw new IllegalArgumentException("Delegate type must be an Interface type");
        }

        final InvocationHandler handler = (proxy, method, args) -> {
            if (allowed(accessor, delegate, method, args)) {
                try {
                    return method.invoke(delegate, args);
                } catch (InvocationTargetException e) {
                    throw e.getCause();
                }
            } else {
                throw new ACLException("Access denies as per ACL");
            }
        };

        return (Q) Proxy.newProxyInstance(dType.getClassLoader(), new Class[]{dType}, handler);
    }
}

用法示例:

package com.example;

import java.lang.reflect.Method;

public class Main {

    interface API {
        void doAlpha(int arg);

        void doBeta(String arg);

        void doGamma(Object arg);
    }

    static class MyAPI implements API {
        @Override
        public void doAlpha(int arg) {
            System.out.println("Alpha");
        }

        @Override
        public void doBeta(String arg) {
            System.out.println("Beta");
        }

        @Override
        public void doGamma(Object arg) {
            System.out.println("Gamma");
        }
    }

    static class AlphaClient {
        void use(API api) {
            api.doAlpha(100);
            api.doBeta("100");
            api.doGamma(this);
        }
    }

    public static class MyACL implements ACL<AlphaClient, API> {
        @Override
        public boolean allowed(AlphaClient accessor, API target, Method method, Object[] args) {
            final String callerName = accessor.getClass().getName().toLowerCase();
            final String methodName = method.getName().toLowerCase().replace("do", "");
            return callerName.contains(methodName);
        }
    }


    public static void main(String[] args) {
        final MyACL acl = new MyACL();
        final API api = new MyAPI();
        final AlphaClient client = new AlphaClient();

        final API guardedAPI = acl.protect(client, api, API.class);
        client.use(guardedAPI);
    }
}

备注:

  1. accessor不一定是客户端对象本身,可以是帮助ACL识别客户端的字符串key或token。

  2. 这里的 ACL 实现是基本的,更有趣的可能是从某个文件读取 ACL 的实现,或者使用方法和客户端注释作为规则的实现。

  3. 如果不想为APIclass定义接口,可以考虑用javassist这样的工具直接代理一个class .

  4. 考虑其他流行的 Aspect Oriented Programming 解决方案

您似乎对 Classes 和接口的用途有点困惑。据我所知,接口是定义软件提供哪些功能的合同。这是来自官方 java 教程:

There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts.

然后你可以写一个Class来实现这个Interface/contract,也就是说,提供实际执行指定内容的代码。 List 接口和 ArrayList class 都是一个例子。

接口和 Classes 具有访问修饰符,但它们并非旨在为特定客户端指定权限。它们根据定义的位置指定其他软件可见的内容:Class、Package、Subclass、World。例如,私有方法只能在定义它的 class 内部访问。

再次来自官方Java教程:

Access level modifiers determine whether other classes can use a particular field or invoke a particular method. There are two levels of access control:

  • At the top level—public, or package-private (no explicit modifier).
  • At the member level—public, private, protected, or package-private (no explicit modifier).

也许您想要更强大的功能,例如 Access Control List (ACL)

你的问题有点不清楚,导致可能有不同的答案。我将尝试涵盖一些可能的领域:

对象封装

如果您的目标是向仅提供特定功能或特定视图的不同客户端提供接口,则有多种解决方案。哪种匹配效果最好取决于您 class:

的目的

重构

这个问题不知何故表明您的 class 负责不同的任务。这可能是一个指标,您可以将其拆分为提供不同接口的不同 classes。

原创

class AllInOne {
    A m1() {}
    B m2() {}
    C m3() {}
}
client1.useClass(allInOneInstance);
client2.useClass(allInOneInstance);
client3.useClass(allInOneInstance);

派生

class One {
    A m1() {}
}

class Two {
    B m2() {}
}

class Three {
    C m3() {}
}
client1.useClass(oneInstance);
client2.useClass(twoInstance);
client3.useClass(threeInstance);

接口

如果您选择将 class 放在一起(可能有充分的理由),您可以让 class 实现对不同客户端所需的视图建模的接口。通过将适当接口的实例传递给客户端,他们将看不到完整的 class 接口:

例子

class AllInOne implements I1, I2, I3 {
    ...
}
interface I1 {
    A m1();
}

但请注意,客户仍然可以像 ((AllInOne) i1Instance).m2().

那样投射到完整的 class

继承

这已经在其他答案中概述过了。因此,我将在这里跳过。我认为这不是一个好的解决方案,因为它在很多情况下很容易崩溃。

委派

如果强制转换对您来说存在风险,您可以创建 class只提供所需接口并委托给实际实现的实体:

例子

class Delegate1 {
    private AllInOne allInOne;
    public A m1() {
        return allInOne.m1();
    }
}

可以通过多种方式实现这一点,具体取决于您的环境,例如显式 classes、动态代理、代码生成……

框架

如果您正在使用像 Spring 这样的应用程序框架,您也许可以使用此框架中的功能。

方面

AOP 允许您拦截方法调用并因此在那里应用一些访问控制逻辑。

安全

请注意,上述所有解决方案都不会为您提供真正的安全保障。使用转换、反射或其他技术仍将允许客户端获得对全部功能的访问权限。

如果您需要更强的访问限制,我将简要概述一些技术,因为它们可能取决于您的环境并且更复杂。

Class 装载机

使用不同的 class 加载器,您可以确保您的部分代码无法访问其范围之外的 class 定义(例如在 tomcat 中用于隔离不同的部署)。

安全管理器

Java 提供了实现您自己的功能的可能性 SecurityManager 这提供了添加一些额外级别的访问检查的方法。

自定义构建安全性

当然您可以添加自己的访问检查逻辑。但我认为这不是 JVM 方法访问的可行解决方案。