diff --git a/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java index 6f19a9d06d..9586948ac4 100644 --- a/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/PropertyAccessingMethodInterceptor.java @@ -20,8 +20,10 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; +import org.springframework.core.BridgeMethodResolver; import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -63,7 +65,8 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro PropertyDescriptor descriptor = BeanUtils.findPropertyForMethod(method); if (descriptor == null) { - throw new IllegalStateException("Invoked method is not a property accessor"); + throw new IllegalStateException("Invoked method '%s' is not a property accessor on '%s'" + .formatted(invocation.getMethod(), target.getWrappedClass().getName())); } if (!isSetterMethod(method, descriptor)) { @@ -84,9 +87,14 @@ private static boolean isSetterMethod(Method method, PropertyDescriptor descript private static Method lookupTargetMethod(MethodInvocation invocation, Class targetType) { - Method method = BeanUtils.findMethod(targetType, invocation.getMethod().getName(), - invocation.getMethod().getParameterTypes()); + Method invokedMethod = invocation.getMethod(); + Method method = BeanUtils.findMethod(targetType, invokedMethod.getName(), invokedMethod.getParameterTypes()); + + if (method == null) { + return invokedMethod; + } - return method != null ? method : invocation.getMethod(); + return BridgeMethodResolver.findBridgedMethod(method); } + } diff --git a/src/test/java/org/springframework/data/projection/PropertyAccessingMethodInterceptorUnitTests.java b/src/test/java/org/springframework/data/projection/PropertyAccessingMethodInterceptorUnitTests.java index d09f4b1ea6..ddd71cc57e 100755 --- a/src/test/java/org/springframework/data/projection/PropertyAccessingMethodInterceptorUnitTests.java +++ b/src/test/java/org/springframework/data/projection/PropertyAccessingMethodInterceptorUnitTests.java @@ -121,6 +121,19 @@ void detectsKotlinPropertiesWithLeadingIsOnTargetType() throws Throwable { assertThat(new PropertyAccessingMethodInterceptor(source).invoke(invocation)).isEqualTo(true); } + @Test // GH-3697 + void considersPropertyDescriptorsFromPackageProtectedSuperclass() throws Throwable { + + var source = new SomeExposedClass(); + source.setFirstname("Walter"); + + when(invocation.getMethod()).thenReturn(Projection.class.getMethod("getFirstname")); + + Object result = new PropertyAccessingMethodInterceptor(source).invoke(invocation); + + assertThat(result).isEqualTo(source.getFirstname()); + } + static class Source { String firstname; @@ -138,4 +151,30 @@ interface Projection { String someGarbage(); } + + static class SomeBaseclass { + + private String firstname; + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + } + + public static class SomeExposedClass extends SomeBaseclass { + + private String lastname; + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + } }