Почему самовывоз не работает для прокси Spring (например, с AOP)? - PullRequest
1 голос
/ 16 июня 2019

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

public class DummyPrinter {
    public void print1() {
        System.out.println("print1");
    }

    public void print2() {
        System.out.println("print2");
    }

    public void printBoth() {
        print1();
        print2();
    }
}
public class PrinterProxy extends DummyPrinter {
    @Override
    public void print1() {
        System.out.println("Before print1");
        super.print1();
    }

    @Override
    public void print2() {
        System.out.println("Before print2");
        super.print2();
    }

    @Override
    public void printBoth() {
        System.out.println("Before print both");
        super.printBoth();
    }
}
public class Main {
    public static void main(String[] args) {
        DummyPrinter p = new PrinterProxy();
        p.printBoth();
    }
}

Выход:

Before print both
Before print1
print1
Before print2
print2

Здесь каждый метод вызывается через прокси. Почему в документации упоминается, что AspectJ следует использовать в случае самостоятельного вызова?

1 Ответ

1 голос
/ 16 июня 2019

Пожалуйста, прочитайте эту главу в руководстве по Spring, тогда вы поймете. Здесь даже используется термин «самообращение». Если вы все еще не понимаете, не стесняйтесь задавать дополнительные вопросы, если они находятся в контексте.


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

Ваше (ложное) предположение о том, как это работает

Ваше недоразумение связано с тем, как работают динамические прокси, потому что они не работают так, как в вашем примере кода. Позвольте мне добавить идентификатор объекта (хеш-код) к выходу журнала для иллюстрации к вашему собственному коду:

package de.scrum_master.app;

public class DummyPrinter {
  public void print1() {
    System.out.println(this + " print1");
  }

  public void print2() {
    System.out.println(this + " print2");
  }

  public void printBoth() {
    print1();
    print2();
  }
}
package de.scrum_master.app;

public class PseudoPrinterProxy extends DummyPrinter {
  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    super.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    super.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    super.printBoth();
  }

  public static void main(String[] args) {
    new PseudoPrinterProxy().printBoth();
  }
}

Журнал консоли:

de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print both
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print2
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print2

См? Всегда есть один и тот же идентификатор объекта, что неудивительно. Самовывоз для вашего «прокси» (который на самом деле не прокси, а статически скомпилированный подкласс) работает из-за полиморфизма . Об этом позаботится компилятор Java.

Как это действительно работает

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

  • Прокси JDK работают для классов, реализующих интерфейсы, что означает, что классы, реализующие эти интерфейсы, создаются во время выполнения. В этом случае суперкласс в любом случае отсутствует, что также объясняет, почему он работает только для открытых методов: интерфейсы имеют только открытые методы.
  • Прокси-серверы CGLIB также работают для классов, не реализующих никаких интерфейсов, и, таким образом, также работают с защищенными и пакетно-ориентированными методами (не частными, хотя, поскольку вы не можете переопределить их, таким образом, термин private).
  • Важным моментом, однако, является то, что в обоих вышеупомянутых случаях исходный объект уже (и до сих пор) существует при создании прокси, таким образом, не существует такой вещи, как полиморфизм . Ситуация такова, что у нас есть динамически созданный прокси-объект, делегирующий исходный объект, то есть у нас есть два объекта: прокси и делегат .

Я хочу проиллюстрировать это так:

package de.scrum_master.app;

public class DelegatingPrinterProxy extends DummyPrinter {
  DummyPrinter delegate;

  public DelegatingPrinterProxy(DummyPrinter delegate) {
    this.delegate = delegate;
  }

  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    delegate.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    delegate.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    delegate.printBoth();
  }

  public static void main(String[] args) {
    new DelegatingPrinterProxy(new DummyPrinter()).printBoth();
  }
}

Видите разницу? Следовательно, журнал консоли изменяется на:

de.scrum_master.app.DelegatingPrinterProxy@59f95c5d Before print both
de.scrum_master.app.DummyPrinter@5c8da962 print1
de.scrum_master.app.DummyPrinter@5c8da962 print2

Такое поведение вы видите в Spring AOP или других частях Spring, использующих динамические прокси, или даже в приложениях, отличных от Spring, использующих прокси JDK или CGLIB в целом.

Это функция или ограничение? Я, как пользователь AspectJ (не Spring AOP), считаю, что это ограничение. Может быть, кто-то еще может подумать, что это особенность, потому что из-за способа использования прокси в Spring вы можете в принципе (не) регистрировать советы по аспектам или перехватчики динамически во время выполнения, то есть у вас есть один прокси на исходный объект (делегат), но для каждого прокси есть динамический список перехватчиков, вызываемых до и / или после вызова исходного метода делегата. Это может быть хорошо в очень динамичных условиях. Я понятия не имею, как часто вы можете использовать это. Но в AspectJ у вас также есть if() указатель pointcut, с помощью которого вы можете определить во время выполнения, применять ли определенные советы (язык AOP для перехватчиков) или нет.

...