Yanming Zhou opened SPR-17310 and commented

Persistable   <-  BaseUser   <-  User
BaseManager<T extends Persistable>   <-  BaseUserManager<T extends BaseUser>  <-     UserManager<User> 

BaseManagerImpl<T extends Persistable>  <-  BaseUserManagerImpl<T extends BaseUser>  <-     UserManagerImpl<User>

The mostSpecificMethod should return method UserManagerImpl.save(User), but return BaseUserManagerImpl.save(BaseUser) which doesn't annotated, it will cause @annotation pointcut broken.


Affects: 5.1 GA

Attachments: - DeclaringClassTest.zip (10.66 kB) - spr17310.zip (10.24 kB)

Comment From: spring-projects-issues

Yanming Zhou commented

It seems eclipse compiler bug. I'm not sure it has relation with https://bugs.eclipse.org/bugs/show_bug.cgi?id=495396

Comment From: spring-projects-issues

Yanming Zhou commented

I have reported https://bugs.eclipse.org/bugs/show_bug.cgi?id=539651

Comment From: spring-projects-issues

Yanming Zhou commented

Finally I figure it out, method.getDeclaringClass() return wrong class with eclipse compiler, it cause spring ClassUtils.getMostSpecificMethod() return wrong method.

@Test
public void test() throws Exception {
    Class<?> targetClass = UserManagerImpl.class;
    Method method = targetClass.getMethod("save", Persistable.class);
    assertEquals(targetClass, method.getDeclaringClass()); // eclipse compiler fail
}

Comment From: spring-projects-issues

Yanming Zhou commented

javac create two bridge methods:

public void com.example.UserManagerImpl.save(com.example.Persistable)
public void com.example.UserManagerImpl.save(com.example.BaseUser)

ecj create only one bridge method:

public void com.example.UserManagerImpl.save(com.example.BaseUser)

It seems a corner case that JLS not covered, maybe it's not a bug of eclipse compiler, Juergen Hoeller could you make some changes to keep compatibility with both of them?

Comment From: spring-projects-issues

Yanming Zhou commented

Workaround:

add two lines before return statement

resolvedMethod = BridgeMethodResolver.findBridgedMethod(resolvedMethod);
resolvedMethod = ClassUtils.getMostSpecificMethod(resolvedMethod, specificTargetClass);

https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java#L198

Comment From: quaff

Minimized unit test, It will fail in eclipse.

import static org.junit.Assert.assertEquals;

import java.lang.reflect.Method;

import org.junit.Test;
import org.springframework.aop.support.AopUtils;

public class AopUtilsTest {

    @Test
    public void testGetMostSpecificMethod() throws Exception {
        String methodName = "feed";
        Class<?> targetClass = DogService.class;
        Method targetMethod = targetClass.getDeclaredMethod(methodName, Dog.class);
        Method originalMethod = AnimalService.class.getDeclaredMethod(methodName, Animal.class);
        Method actualMethod = AopUtils.getMostSpecificMethod(originalMethod, targetClass);
        assertEquals("Please ensure class compiled with javac not eclipse", targetMethod, actualMethod);
    }

    public static class Animal {

    }

    public static abstract class Mammal extends Animal {

    }

    public static class Dog extends Mammal {

    }

    public static class AnimalService<T extends Animal> {
        public void feed(T obj) {
        }
    }

    public static class MammalService<T extends Mammal> extends AnimalService<T> {
        @Override
        public void feed(T obj) {
        }
    }

    public static class DogService extends MammalService<Dog> {
        @Override
        public void feed(Dog obj) {
        }
    }

}

Comment From: snicoll

Courtesy of @wilkinsona, the test is still failing with the latest Spring Framework 6.1 milestone.

@jhoeller wondering if we could accommodate with this difference between the eclipse and Java compiler?

Comment From: jhoeller

I've addressed this through a new combined BridgeMethodResolver.getMostSpecificMethod(Method, Class) delegate which does ClassUtils.getMostSpecificMethod and then resolves the bridge method against the same target class, being able to find a matching signature even if no bridge method has been generated at the same class hierarchy level (as with the Eclipse compiler).