Как предотвратить вызов публичных методов из определенных классов - PullRequest
15 голосов
/ 30 марта 2011

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

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

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

Ответы [ 10 ]

9 голосов
/ 30 марта 2011

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

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

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

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

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


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


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

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

7 голосов
/ 30 марта 2011

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

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

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

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

6 голосов
/ 30 марта 2011

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

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

2 голосов
/ 02 января 2017

Как уже упоминали другие, использование трассировки стека является одним из способов реализовать функциональность, которую вы ищете.Как правило, если нужно «заблокировать» вызывающих из метода 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 проверяет только имена классов.Добавление функциональности для проверки имен методов требует только нескольких модификаций, поэтому я собираюсь оставить это как упражнение.

2 голосов
/ 30 марта 2011

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

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

2 голосов
/ 30 марта 2011

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

1 голос
/ 30 марта 2011

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

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

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

0 голосов
/ 16 июня 2016

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

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

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

Class Foo:

public class Foo
{
    public Foo()
    {   
        Bar bar = new Bar();
        bar.method(new FooPrivateKey());
    }

    public class FooPrivateKey
    {
        private FooPrivateKey()
        {   }
    }  
}

Class Bar:

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(), но это по крайней мере предупредит программиста чуть более навязчиво, чем простой комментарий или документация, в то время каквам не нужно заглядывать в более сложные вещи, такие как Roberto Trunfio и Ronan Quillevere (которые также являются вполне жизнеспособными ответами, слишком сложными для большинстваситуации на мой взгляд).

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

0 голосов
/ 08 сентября 2015

Вы можете использовать такой инструмент, как 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 или другую систему сборки, это может вызвать ошибку во время процесса сборки.

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

0 голосов
/ 15 мая 2014

Вы можете сделать это, используя аннотации и отражения.Я сообщу о подобном случае, то есть случае, когда вы можете разрешить вызов метода только определенными методами из внешних классов.Предположим, что класс, который должен быть «защищен» при любом вызове его открытых методов, равен 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, только если метод / класс не аннотирован.

...