Affects: Spring AOP 5.0.7.RELEASE

I'm building an application and I'm using aspects to modify some of my business objects. When I'm starting my application I get the following stacktrace (only the root is shown):

java.lang.IllegalArgumentException: methods with same signature get() but incompatible return types: [interface main.DomainObjectInterfaceA, interface main.DomainObjectInterfaceB]
    at sun.misc.ProxyGenerator.checkReturnTypes(ProxyGenerator.java:712)
    at sun.misc.ProxyGenerator.generateClassFile(ProxyGenerator.java:461)
    at sun.misc.ProxyGenerator.generateProxyClass(ProxyGenerator.java:339)
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:639)
    at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:557)
    at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
    at java.lang.reflect.WeakCache.get(WeakCache.java:127)
    at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:419)
    at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:719)
    at org.springframework.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:123)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:473)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:355)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:304)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:439)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1712)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:581)
    ... 10 more

I extracted this in a minimal code snippet from my application to reproduce this stacktrace.

My domain model consists of three classes:

package main;
public interface DomainObjectInterfaceA {
}
package main;
public interface DomainObjectInterfaceB{
}
package main;
public class DomainObjectImplementation implements DomainObjectInterfaceA, DomainObjectInterfaceB {

  private int fieldToModifyThroughAspect = 0;

  public int getFieldToModifyThroughAspect() {
    return fieldToModifyThroughAspect;
  }

  public void setFieldToModifyThroughAspect( int aFieldToModify ) {
    fieldToModifyThroughAspect = aFieldToModify;
  }
}

Furthermore I have the following two Spring components:

package main;
@Component
public class ComponentImplementation implements ComponentInterfaceA<DomainObjectImplementation>, ComponentInterfaceB<DomainObjectImplementation> {

  public DomainObjectImplementation get() {
    return new DomainObjectImplementation();
  }
}

package main;
@org.aspectj.lang.annotation.Aspect
@Component
public class Aspect {

  @Pointcut( "execution(public DomainObjectImplementation main.ComponentImplementation.*(..))" )
  public void anyPublicOperation() {
  }

  @AfterReturning( pointcut = "anyPublicOperation()", returning = "aObject" )
  public Object get( Object aObject ) {
    DomainObjectImplementation returningObject = ( DomainObjectImplementation ) aObject;
    returningObject.setFieldToModifyThroughAspect( 5);
    return returningObject;
  }

}

The crucial part in reproducing the issue are the following two generic interfaces which are implemented by ComponentImplementation:

package main;
public interface ComponentInterfaceA<T extends DomainObjectInterfaceA> {

  T get();

}
package main;
public interface ComponentInterfaceB<T extends DomainObjectInterfaceB> {

  T get();

}

The above two interfaces share a method with the same signature, but a different generic type. ComponentImplementation implements those two interfaces with a class that satisfies both of those generic arguments.

The last two required classes are of course the Main class and the Spring Config class which are declared below:

package main;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan( basePackageClasses = Config.class )
public class Config {
}

package main;
public class Main {

  public static void main( String[] args ) {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( Config.class );
    ComponentImplementation bean = context.getBean( ComponentImplementation.class );
    DomainObjectImplementation domainObject = bean.get();

    System.err.println("Aspect modified get method = " + ( domainObject.getFieldToModifyThroughAspect() == 5));
  }

}

I also tried to configure EnableAspectJAutoProxy with proxyTargetClass = true, but then the same stacktrace is thrown with a slightly different start (namely the JDK proxy vs the Cglib)

The complete dependencies list:

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/aspectj/aspectjrt -->
    <dependency>
      <groupId>aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.5.4</version>
    </dependency>


  </dependencies>

Comment From: sbrannen

Thanks for getting in touch, but it feels like this is a question that would be better suited to Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use the issue tracker only for bugs and enhancements. Feel free to update this issue with a link to the re-posted question (so that other people can find it) or add some more details if you feel this is a genuine bug.


Having quoted our policy above, I am still happy to provide the following information for your particular scenario.

This is to be expected, as per the Javadoc for java.lang.reflect.Proxy.getProxyClass(ClassLoader, Class<?>...):

For any set of member methods of the specified interfaces that have the same signature: - If the return type of any of the methods is a primitive type or void, then all of the methods must have that same return type. - Otherwise, one of the methods must have a return type that is assignable to all of the return types of the rest of the methods.

The latter applies to your scenario. Specifically, DomainObjectInterfaceA and DomainObjectInterfaceB are not compatible.

If you ensure that they have a common super type -- for example, by having DomainObjectInterfaceB extend DomainObjectInterfaceA, then your example application will execute without an exception (if you declare @EnableAspectJAutoProxy(proxyTargetClass = true)).

Hope this helps.

If you have further questions in this regard, please post something on Stack Overflow.

Comment From: kriegaex

I found this via StackOverflow question #59486425 and became interested. Actually I can reproduce it, but the problem goes away with proxyTargetClass = true without changing any code. I think someone should look into this and maybe treat it as a bug because the behaviour is different for JDK vs. CGLIB proxies.

I created an MCVE and pushed it to a GitHub repo for your convenience.

Comment From: sbrannen

Thanks for the feedback and MCVE @kriegaex.

I suppose my answer was a bit misleading.

You're correct: no code changes are necessary if you configure proxyTargetClass = true.

The common supertype is only required when using JDK dynamic proxies. Thus, I had mentioned that to explain the underlying cause of the exception encountered and how to avoid the exception when using JDK proxies. Note, however, that the code that retrieves the bean from the context would need to be reworked when using JDK proxies, since the ComponentImplementation class is not an interface implemented by the proxy.

I think someone should look into this and maybe treat it as a bug because the behaviour is different for JDK vs. CGLIB proxies.

We cannot treat it as a bug, because it cannot be fixed. The behavior is simply a limitation of dynamic proxies which are based solely on interfaces.

Comment From: kriegaex

@sbrannen, thanks for your explanation. It was enlightening indeed, now I understand better.

The common supertype is only required when using JDK dynamic proxies.

Yes, but it would not be sufficient to just have DomainObjectInterfaceB extend DomainObjectInterfaceB, as you suggested. There are more things that would need to be reworked towards using interfaces in order to make the code run.

Note, however, that the code that retrieves the bean from the context would need to be reworked when using JDK proxies, since the ComponentImplementation class is not an interface implemented by the proxy.

That's the important part which made my mind "click". Yes, of course! When using interface-based proxies the information of the class implementing two interfaces is simply unavailable because Spring is explicitly configured not to proxy target classes but only interfaces.

We cannot treat it as a bug, because it cannot be fixed. The behavior is simply a limitation of dynamic proxies which are based solely on interfaces.

Yes, you are absolutely right. I hadn't thought it through completely, just read your first answer.