Spring @DeclareParents 注释如何工作?它如何实现新接口的方法?

How does Spring @DeclareParents annotation work? How does it implement the methods from the new interface?

在阅读 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

于是明白了原理:我是在Openable接口中添加一个新的类型(Paintable)。因此,所有可打开的东西(即 Door)在运行时都可以绘制。我想知道的是:Spring 内部是如何做到的?当它为 Door class 创建代理时,它如何实现 Paintable 接口提供的新方法?根据我目前所见,我的建议如下:它使用我在 defaultImpl 属性中提供的 Paintable 实现,即 Fence。它似乎实例化了一个新的 Fence,将其(可能)存储到 Door-proxy 的某个字段中,然后将对 Door 上的 Paintable-方法的所有调用委托给这个内部 Fence 对象。我想知道这个建议是否正确?不幸的是,文档中没有关于它的详细描述。

如果您向主 class 添加更多日志输出...

// (...)
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]

因此您看到动态 CGLIB 代理扩展了 Door,这并不奇怪。然后Spring让代理实现一些AOP相关的接口,还有Paintable。就这样,很简单。

在调试器中,您还可以看到一些字段,例如 CGLIB$CALLBACK_0CGLIB$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

后记:现在你知道的更多了,但实际上你真的不需要知道,因为它是关于Spring内部的。您在 Spring 手册中找不到它,因为内部实现理论上可以随时更改。此外,如果您按照 Spring 手册中的描述从 Spring AOP 切换到完整的 AspectJ,那么所有这些信息都是无效的,因为 AspectJ 不使用代理而是直接转换 Java字节码。那么我的答案就会大不一样了。


更新:你问:

But the question was actually, how Spring knows, what the implementation of the new methods should be. In other words, now that Door implements Paintable, how does Spring determine the implementation of Paintable's methods on the Door class?

它使代理调用Fence的相应方法,因为您在切面中将其指定为defaultImpl。为此,Spring 创建一个内部 Fence 委托 实例,通过反射在其上调用方法。你可以看到,如果你调试到一个像

这样的方法调用
doorAsPaintable.paint(Color.GREEN);

直到你到达方法

package org.springframework.aop.support;

class DelegatePerTargetObjectIntroductionInterceptor ...

  public Object invoke(MethodInvocation mi)

如有其他问题,请阅读 Spring 源代码。