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

DanielBK :

While reading through the Spring AOP documentation I came across the @DeclareParents annotation. I was able to construct a working example using it:

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

The output:

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

So I understand the principle: I am adding a new type (Paintable) to the Openable interface. And thus everything that is openable (namely a Door) becomes paintable at runtime. What I am wondering about is: How does Spring do it internally? When it creates the proxy for the Door class, how does it implement the new methods provided by the Paintable interface? Based on what I have seen so far, my suggestion would be the following: It uses the Paintable-implementation which I provided in the defaultImpl attribute, which is Fence. It seems to instantiate a new Fence, store it (maybe) into some field onto the Door-proxy and then delegate all calls to Paintable-methods on the Door to this internal Fence object. I would like to know, if this suggestion is correct? Unfortunately, there is no detailed description on it in the documentation.

kriegaex :

If you add some more log output to your main 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()));
// (...)

... then you will see this in your log output (sorry, I used other package names than you in my sample app):

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]

So you see the dynamic CGLIB proxy extends Door, no surprise here. Then Spring makes the proxy implement a few AOP-related interfaces and also Paintable. That's it, quite simple.

In a debugger you can also see a few fields like CGLIB$CALLBACK_0 to CGLIB$CALLBACK_4. In my local environment #4 is the interesting one. It is a CglibAopProxy$AdvisedDispatcher instance and has a field advised which is a ProxyFactory instance with this value (adding line breaks for readability):

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: Now you know more but actually you really do not need to know because it is about Spring internals. You do not find it in the Spring manual because the internal implementation could theoretically change at any time. And besides, if you switch from Spring AOP to full AspectJ as described in the Spring manual, then all this information is null and void because AspectJ does not use proxies but directly transforms the Java bytecode. Then my answer would look much different.


Update: You asked:

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?

It makes the proxy call the corresponding methods of Fence because you specified it as defaultImpl in your aspect. For that purpose Spring creates an internal Fence delegate instance on which to call the method via reflection. You can see that if you debug into a method call like

doorAsPaintable.paint(Color.GREEN);

until you reach method

package org.springframework.aop.support;

class DelegatePerTargetObjectIntroductionInterceptor ...

  public Object invoke(MethodInvocation mi)

For further questions please read the Spring source code.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=417377&siteId=1