Как работает аннотация Spring @DeclareParents? Как он реализует методы из нового интерфейса? - PullRequest
2 голосов
/ 14 февраля 2020

При чтении документации Spring AOP я наткнулся на аннотацию @DeclareParents. Я смог построить рабочий пример, используя его:

public interface Openable {

  void open();
  void close();
  boolean isOpen();

}
public interface Paintable {

  void paint(Color color);
  Color getColor();

}
@Component
public class Door implements Openable {

  private boolean isOpen = false;
  private Color color;

  @Override
  public void open() {
    isOpen = true;
  }

  @Override
  public void close() {
    isOpen = false;
  }

  @Override
  public boolean isOpen() {
    return isOpen;
  }
}
@Component
public class Fence implements Paintable {

  private Color color;

  @Override
  public void paint(Color color) {
    this.color = color;
  }

  @Override
  public Color getColor() {
    return color;
  }
}
@Component
@Aspect
public class IntroductionAspect {

  @DeclareParents(value="aopTraining.IntrocuctionsTest.Openable+", defaultImpl=Fence.class)
  public static Paintable openable;
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan
public class IntroductionsAppConfig {

}
public class Main {

  public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IntroductionsAppConfig.class);

    Fence fence = context.getBean(Fence.class);
    System.out.println("Color of the fence: " + fence.getColor());

    Paintable doorAsPaintable = (Paintable) context.getBean(Door.class);
    Openable doorAsOpenable = (Openable) doorAsPaintable; 
    System.out.println("Door is open: " + doorAsOpenable.isOpen());
    doorAsOpenable.open();
    System.out.println("Door is open: " + doorAsOpenable.isOpen());
    System.out.println("Door's current color: " + doorAsPaintable.getColor());
    doorAsPaintable.paint(Color.GREEN);
    System.out.println("Door's current color: " + doorAsPaintable.getColor());

    System.out.println("Color of the fence: " + fence.getColor());
  }
}

Вывод:

Color of the fence: null
Door is open: false
Door is open: true
Door's current color: null
Door's current color: java.awt.Color[r=0,g=255,b=0]
Color of the fence: null

Итак, я понимаю принцип: я добавляю новый тип (Paintable) к интерфейсу Openable. И, таким образом, все, что можно открыть (а именно Door), становится доступным для рисования во время выполнения. Что меня интересует, так это то, как Spring делает это внутренне? Когда он создает прокси для класса Door, как он реализует новые методы, предоставляемые интерфейсом Paintable? Исходя из того, что я видел до сих пор, мое предложение будет следующим: он использует реализацию Paintable, которую я предоставил в атрибуте defaultImpl, то есть Fence. Кажется, он создает новый Fence, сохраняет его (возможно) в каком-то поле на Door -прокси и затем делегирует все вызовы Paintable -методам Door этому внутреннему Fence объекту. Я хотел бы знать, правильно ли это предложение? К сожалению, подробного описания в документации нет.

1 Ответ

1 голос
/ 14 февраля 2020

Если вы добавите еще несколько выводов журнала в свой основной класс ...

// (...)
Paintable doorAsPaintable = (Paintable) context.getBean(Door.class);
Openable doorAsOpenable = (Openable) doorAsPaintable;

Class<?> dynamicProxyClass = doorAsPaintable.getClass();
System.out.println("Dynamic proxy: " + dynamicProxyClass);
System.out.println("Dynamic proxy parent: " + dynamicProxyClass.getSuperclass());
System.out.println("Dynamic proxy interfaces: " + Arrays.asList(dynamicProxyClass.getInterfaces()));
// (...)

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

Dynamic proxy: class spring.aop.q60221207.Door$$EnhancerBySpringCGLIB$$a29f3532
Dynamic proxy parent: class spring.aop.q60221207.Door
Dynamic proxy interfaces: [interface spring.aop.q60221207.Paintable, interface org.springframework.aop.SpringProxy, interface org.springframework.aop.framework.Advised, interface org.springframework.cglib.proxy.Factory]

Итак, вы видите, что динамический c CGLIB-прокси расширяется Door, что неудивительно. Затем Spring заставляет прокси-сервер реализовывать несколько интерфейсов, связанных с AOP, а также Paintable. Вот и все, довольно просто.

В отладчике вы также можете увидеть несколько полей, например от CGLIB$CALLBACK_0 до CGLIB$CALLBACK_4. В моей местной среде # 4 интересный. Это экземпляр CglibAopProxy$AdvisedDispatcher с полем advised, представляющим собой экземпляр ProxyFactory с этим значением (с добавлением разрывов строк для удобства чтения):

org.springframework.aop.framework.ProxyFactory:
  1 interfaces [spring.aop.q60221207.Paintable];
  1 advisors [org.springframework.aop.aspectj.DeclareParentsAdvisor@797cf65c];
  targetSource [SingletonTargetSource for target object [spring.aop.q60221207.Door@29526c05]];
  proxyTargetClass=true;
  optimize=false;
  opaque=false;
  exposeProxy=false;
  frozen=false

Postscript: Сейчас вы знаете больше, но на самом деле вам действительно не нужно знать, потому что речь идет о внутренностях Spring. Вы не найдете его в руководстве Spring, потому что внутренняя реализация может теоретически измениться в любое время. И, кроме того, если вы переключитесь с Spring AOP на полный AspectJ, как описано в руководстве по Spring, то вся эта информация будет недействительной, поскольку AspectJ не использует прокси, а непосредственно преобразует байт-код Java. Тогда мой ответ выглядел бы совсем по-другому.


Обновление: Вы спросили:

Но вопрос был на самом деле, откуда Spring знает, какова реализация из новых методов должно быть. Другими словами, теперь, когда Door реализует Paintable, как Spring определяет реализацию методов Paintable для класса Door?

Он заставляет прокси вызывать соответствующие методы Fence, потому что вы задали его как defaultImpl в твоем аспекте. Для этого Spring создает внутренний Fence делегат экземпляр, для которого вызывается метод через отражение. Вы можете видеть, что если вы отлаживаете вызов метода, например

doorAsPaintable.paint(Color.GREEN);

, пока не достигнете метода

package org.springframework.aop.support;

class DelegatePerTargetObjectIntroductionInterceptor ...

  public Object invoke(MethodInvocation mi)

Для дальнейших вопросов, пожалуйста, прочитайте исходный код Spring.

...