Как запретить публичные методы из определенных классов

У меня есть существующий класс, в который я хочу добавить метод. Но я хочу, чтобы метод вызывался только из определенного метода из определенного класса. Есть ли способ предотвратить этот вызов из других классов/методов?

Например, у меня есть существующий класс 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 внутри method(), а затем подтвердить родительский метод?

То, что я ищу, - это решение, которое является более чистым и целесообразным решением, таким как шаблон или что-то в этом роде.

Ответы

Ответ 1

Если честно, вы нарисовали себя в углу.

Если классы A и B не связаны, а не являются членами одного и того же пакета, то видимость не решит проблему. (И даже если бы это было так, отражение может быть использовано для искажения правил видимости.)

Анализ статического кода не решит проблему, если код может использовать отражение для вызова метода.

Передача и проверка B.this в качестве дополнительного параметра на A.method(...) не помогает, потому что другой класс C может передать экземпляр B.

Это оставляет только подход stacktrace... или отказываясь от хорошего смысла программиста 1 чтобы не вызывать методы, которых они не должны.


Идеальное решение - пересмотреть решения по дизайну и/или кодированию, которые привели вас в этот беспорядок.


1 - Не стоит недооценивать здравого смысла программиста. Большинство программистов, когда они видят советы не вызывать какой-либо метод, скорее всего, последуют этому совету.

Ответ 2

Вы можете использовать интерфейс. Если вы проходите в вызывающем классе, вы можете подтвердить, что класс имеет соответствующий тип.

В качестве альтернативы, если вы используете Java, вы можете использовать доступ к уровню "по умолчанию" или "пакетный" (например, void method() и public void method()). Это позволит вызывать ваш метод любым классом внутри пакета и не требует, чтобы вы передали класс методу.

Ответ 3

Правильный способ сделать это будет SecurityManager.

Определите разрешение, которое должен иметь весь код, который хочет вызвать A.method(), а затем убедитесь, что это разрешение имеет только B и A (это также означает, что ни один класс не имеет AllPermission).

В A вы проверяете это с помощью System.getSecurityManager().checkPermission(new BMethodPermission()), а в B вы вызываете метод внутри AccessController.doPrivileged(...).

Конечно, для этого требуется, чтобы был установлен диспетчер безопасности (и он использует подходящие политики) - если это не так, всем кодам доверяют, и каждый может вызвать все (при необходимости, с Reflection).

Ответ 4

Правильно используйте protected

Ответ 5

Единственный способ убедиться в том, что во время выполнения нужно взять трассировку стека. Даже если его частный, вы можете получить доступ к методу с помощью отражений.

Простейшим способом сделать это будет проверка использования в вашей среде IDE. (если его не вызывать через отражения)

Ответ 6

Стандартный способ сделать это в java - это поставить класс B и класс A в один и тот же пакет (возможно, подпакет вашего текущего приложения) и использовать видимость по умолчанию.

По умолчанию java-видимость "package-private" означает, что все в этом пакете может видеть ваш метод, но ничто из этого пакета не может получить к нему доступ.

См. также:
Есть ли способ имитировать концепцию "друга" С++ в Java?

Ответ 7

Вы можете сделать это, используя аннотации и размышления. Я расскажу об аналогичном случае, то есть в случае, когда вы можете позволить методу вызываться только конкретными методами из классов extenal. Предположим, что класс, который должен быть "защищен" любым вызовом его общедоступных методов, Invoked, а Invoker - это класс, который имеет метод, позволяющий вызывать один или несколько методов из Invoked. Затем вы можете сделать что-то вроде сообщений в следующем.

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
    }
   }
}

а класс Invoker -

public class Invoker{
   private Invoked i;

   @Invoked.CanInvoke
   public void methodInvoker(){
     i.methodToBeInvoked();
   }

}

Обратите внимание, что метод, который активируется для вызова, аннотируется аннотацией CanInvoke.

Запрошенный вами запрос аналогичен. Вы комментируете классы/метод, которые не могут вызвать открытый метод, а затем вы устанавливаете true переменную canExecute, только если метод/класс не аннотируется.

Ответ 8

Вы можете использовать инструмент, например Macker и добавить его в свой процесс сборки, чтобы проверить соблюдение некоторых правил, например

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

Это не помешает вам писать неправильный код, но если вы используете Maven или другую систему сборки, это может вызвать ошибку во время процесса сборки.

Эти инструменты работают на уровне "класса" не на уровне "метода", но я не вижу смысла предотвращать вызов только одного метода из определенного класса...

Ответ 9

Я понимаю конкретный метод вашего конкретного случая в конкретном классе, но я не думаю, что вы можете надежно решить это во время разработки (и я не могу придумать пример использования, где это должно быть принудительно соблюдено).

В следующем примере создается простое решение времени разработки для ограничения доступа метода класса к определенному классу. Однако его можно легко расширить до нескольких разрешенных классов.

Это достигается путем определения внутреннего внутреннего класса с частным конструктором, который действует как ключ к используемому методу. В следующем примере класс Bar имеет метод, который должен вызываться только из экземпляра класса Foo.

Класс Foo:

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.
    }
}

Я не думаю, что это безопасно для таких вещей, как отражение или даже такие вещи, как FooPrivateKey.class.newInstance(), но это, по крайней мере, предупредит программиста немного более навязчиво, чем простой комментарий или документацию, в то время как вы не нужно искать более сложные вещи, например, такие, как предложенные такими людьми, как Роберто Трунфио и Ронан Киллевере (который также являются вполне жизнеспособными ответами, слишком сложными для большинства ситуаций, на мой взгляд).

Надеюсь, этого достаточно для вашего случая использования.

Ответ 10

Как уже упоминалось, использование трассировки стека является одним из способов реализации функциональности, которую вы ищете. Как правило, если нужно "блокировать" вызывающих абонентов методом public, это может быть признаком плохого дизайна. Как правило, используйте модификаторы доступа, которые максимально ограничивают область действия. Однако создание метода package-private или protected не всегда является опцией. Иногда бывает необходимо сгруппировать некоторые классы в отдельный пакет. В этом случае доступ по умолчанию (пакет-частный) слишком ограничительный, и обычно это не имеет смысла для подкласса, поэтому protected тоже не полезно.

Если требуется ограничить вызов определенным классам, вы можете создать такой метод, как:

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;
}

Итак, если метод stop вызывается классом, отличным от ShutdownHandler, checkPermission будет вызывать IllegalStateException.

Вы можете задаться вопросом, почему checkPermission жестко закодирован для использования четвертого элемента трассировки стека. Это связано с тем, что Thread#getStackTrace() делает последний вызванный метод первым элементом. Таким образом,

  • getStackTrace()[0] будет вызовом самой getStackTrace.
  • getStackTrace()[1] будет вызов checkPermission.
  • getStackTrace()[2] будет вызов stop.
  • getStackTrace()[3] будет метод, который называется stop. Это нас интересует.

Вы упомянули, что хотите, чтобы методы вызывались из определенного класса и метода, но checkPermission проверяет только имена классов. Добавление функциональности для проверки имен методов требует лишь нескольких модификаций, поэтому я собираюсь оставить это как упражнение.