Browse Source

Find interface method even for late-bound interface declaration in subclass

Closes gh-27995
pull/28119/head
Juergen Hoeller 3 years ago
parent
commit
bc9cd9a687
  1. 4
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
  2. 10
      spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java
  3. 60
      spring-core/src/main/java/org/springframework/util/ClassUtils.java
  4. 14
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java
  5. 8
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java
  6. 24
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

4
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -1908,7 +1908,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'");
} }
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass());
if (System.getSecurityManager() != null) { if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> { AccessController.doPrivileged((PrivilegedAction<Object>) () -> {

10
spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -138,7 +138,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
beanName + "' has a non-boolean parameter - not supported as destroy method"); beanName + "' has a non-boolean parameter - not supported as destroy method");
} }
} }
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass());
} }
this.destroyMethod = destroyMethod; this.destroyMethod = destroyMethod;
} }
@ -252,9 +252,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
invokeCustomDestroyMethod(this.destroyMethod); invokeCustomDestroyMethod(this.destroyMethod);
} }
else if (this.destroyMethodName != null) { else if (this.destroyMethodName != null) {
Method methodToInvoke = determineDestroyMethod(this.destroyMethodName); Method destroyMethod = determineDestroyMethod(this.destroyMethodName);
if (methodToInvoke != null) { if (destroyMethod != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke)); invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass()));
} }
} }
} }

60
spring-core/src/main/java/org/springframework/util/ClassUtils.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2021 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -1256,7 +1256,7 @@ public abstract class ClassUtils {
* (may be {@code null} or may not even implement the method) * (may be {@code null} or may not even implement the method)
* @return the specific target method, or the original method if the * @return the specific target method, or the original method if the
* {@code targetClass} does not implement it * {@code targetClass} does not implement it
* @see #getInterfaceMethodIfPossible * @see #getInterfaceMethodIfPossible(Method, Class)
*/ */
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) { public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) { if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) {
@ -1289,28 +1289,54 @@ public abstract class ClassUtils {
* @param method the method to be invoked, potentially from an implementation class * @param method the method to be invoked, potentially from an implementation class
* @return the corresponding interface method, or the original method if none found * @return the corresponding interface method, or the original method if none found
* @since 5.1 * @since 5.1
* @see #getMostSpecificMethod * @deprecated in favor of {@link #getInterfaceMethodIfPossible(Method, Class)}
*/ */
@Deprecated
public static Method getInterfaceMethodIfPossible(Method method) { public static Method getInterfaceMethodIfPossible(Method method) {
return getInterfaceMethodIfPossible(method, null);
}
/**
* Determine a corresponding interface method for the given method handle, if possible.
* <p>This is particularly useful for arriving at a public exported type on Jigsaw
* which can be reflectively invoked without an illegal access warning.
* @param method the method to be invoked, potentially from an implementation class
* @param targetClass the target class to check for declared interfaces
* @return the corresponding interface method, or the original method if none found
* @since 5.3.16
* @see #getMostSpecificMethod
*/
public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class<?> targetClass) {
if (!Modifier.isPublic(method.getModifiers()) || method.getDeclaringClass().isInterface()) { if (!Modifier.isPublic(method.getModifiers()) || method.getDeclaringClass().isInterface()) {
return method; return method;
} }
return interfaceMethodCache.computeIfAbsent(method, key -> { // Try cached version of method in its declaring class
Class<?> current = key.getDeclaringClass(); Method result = interfaceMethodCache.computeIfAbsent(method,
while (current != null && current != Object.class) { key -> findInterfaceMethodIfPossible(key, key.getDeclaringClass(), Object.class));
Class<?>[] ifcs = current.getInterfaces(); if (result == method && targetClass != null) {
for (Class<?> ifc : ifcs) { // No interface method found yet -> try given target class (possibly a subclass of the
try { // declaring class, late-binding a base class method to a subclass-declared interface:
return ifc.getMethod(key.getName(), key.getParameterTypes()); // see e.g. HashMap.HashIterator.hasNext)
} result = findInterfaceMethodIfPossible(method, targetClass, method.getDeclaringClass());
catch (NoSuchMethodException ex) { }
// ignore return result;
} }
private static Method findInterfaceMethodIfPossible(Method method, Class<?> startClass, Class<?> endClass) {
Class<?> current = startClass;
while (current != null && current != endClass) {
Class<?>[] ifcs = current.getInterfaces();
for (Class<?> ifc : ifcs) {
try {
return ifc.getMethod(method.getName(), method.getParameterTypes());
}
catch (NoSuchMethodException ex) {
// ignore
} }
current = current.getSuperclass();
} }
return key; current = current.getSuperclass();
}); }
return method;
} }
/** /**

14
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -58,8 +58,18 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
* @param method the method to invoke * @param method the method to invoke
*/ */
public ReflectiveMethodExecutor(Method method) { public ReflectiveMethodExecutor(Method method) {
this(method, null);
}
/**
* Create a new executor for the given method.
* @param method the method to invoke
* @param targetClass the target class to invoke the method on
* @since 5.3.16
*/
public ReflectiveMethodExecutor(Method method, @Nullable Class<?> targetClass) {
this.originalMethod = method; this.originalMethod = method;
this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method); this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method, targetClass);
if (method.isVarArgs()) { if (method.isVarArgs()) {
this.varargsPosition = method.getParameterCount() - 1; this.varargsPosition = method.getParameterCount() - 1;
} }

8
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -176,7 +176,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
} }
if (matchInfo != null) { if (matchInfo != null) {
if (matchInfo.isExactMatch()) { if (matchInfo.isExactMatch()) {
return new ReflectiveMethodExecutor(method); return new ReflectiveMethodExecutor(method, type);
} }
else if (matchInfo.isCloseMatch()) { else if (matchInfo.isCloseMatch()) {
if (this.useDistance) { if (this.useDistance) {
@ -204,13 +204,13 @@ public class ReflectiveMethodResolver implements MethodResolver {
} }
} }
if (closeMatch != null) { if (closeMatch != null) {
return new ReflectiveMethodExecutor(closeMatch); return new ReflectiveMethodExecutor(closeMatch, type);
} }
else if (matchRequiringConversion != null) { else if (matchRequiringConversion != null) {
if (multipleOptions) { if (multipleOptions) {
throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name);
} }
return new ReflectiveMethodExecutor(matchRequiringConversion); return new ReflectiveMethodExecutor(matchRequiringConversion, type);
} }
else { else {
return null; return null;

24
spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -139,7 +139,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
// The readerCache will only contain gettable properties (let's not worry about setters for now). // The readerCache will only contain gettable properties (let's not worry about setters for now).
Property property = new Property(type, method, null); Property property = new Property(type, method, null);
TypeDescriptor typeDescriptor = new TypeDescriptor(property); TypeDescriptor typeDescriptor = new TypeDescriptor(property);
method = ClassUtils.getInterfaceMethodIfPossible(method); method = ClassUtils.getInterfaceMethodIfPossible(method, type);
this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor));
this.typeDescriptorCache.put(cacheKey, typeDescriptor); this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true; return true;
@ -182,7 +182,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
// The readerCache will only contain gettable properties (let's not worry about setters for now). // The readerCache will only contain gettable properties (let's not worry about setters for now).
Property property = new Property(type, method, null); Property property = new Property(type, method, null);
TypeDescriptor typeDescriptor = new TypeDescriptor(property); TypeDescriptor typeDescriptor = new TypeDescriptor(property);
method = ClassUtils.getInterfaceMethodIfPossible(method); method = ClassUtils.getInterfaceMethodIfPossible(method, type);
invoker = new InvokerPair(method, typeDescriptor); invoker = new InvokerPair(method, typeDescriptor);
this.lastReadInvokerPair = invoker; this.lastReadInvokerPair = invoker;
this.readerCache.put(cacheKey, invoker); this.readerCache.put(cacheKey, invoker);
@ -242,7 +242,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
// Treat it like a property // Treat it like a property
Property property = new Property(type, null, method); Property property = new Property(type, null, method);
TypeDescriptor typeDescriptor = new TypeDescriptor(property); TypeDescriptor typeDescriptor = new TypeDescriptor(property);
method = ClassUtils.getInterfaceMethodIfPossible(method); method = ClassUtils.getInterfaceMethodIfPossible(method, type);
this.writerCache.put(cacheKey, method); this.writerCache.put(cacheKey, method);
this.typeDescriptorCache.put(cacheKey, typeDescriptor); this.typeDescriptorCache.put(cacheKey, typeDescriptor);
return true; return true;
@ -291,7 +291,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (method == null) { if (method == null) {
method = findSetterForProperty(name, type, target); method = findSetterForProperty(name, type, target);
if (method != null) { if (method != null) {
method = ClassUtils.getInterfaceMethodIfPossible(method); method = ClassUtils.getInterfaceMethodIfPossible(method, type);
cachedMember = method; cachedMember = method;
this.writerCache.put(cacheKey, cachedMember); this.writerCache.put(cacheKey, cachedMember);
} }
@ -533,21 +533,21 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (target == null) { if (target == null) {
return this; return this;
} }
Class<?> clazz = (target instanceof Class ? (Class<?>) target : target.getClass()); Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass());
if (clazz.isArray()) { if (type.isArray()) {
return this; return this;
} }
PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
InvokerPair invocationTarget = this.readerCache.get(cacheKey); InvokerPair invocationTarget = this.readerCache.get(cacheKey);
if (invocationTarget == null || invocationTarget.member instanceof Method) { if (invocationTarget == null || invocationTarget.member instanceof Method) {
Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); Method method = (Method) (invocationTarget != null ? invocationTarget.member : null);
if (method == null) { if (method == null) {
method = findGetterForProperty(name, clazz, target); method = findGetterForProperty(name, type, target);
if (method != null) { if (method != null) {
TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1)); TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1));
method = ClassUtils.getInterfaceMethodIfPossible(method); method = ClassUtils.getInterfaceMethodIfPossible(method, type);
invocationTarget = new InvokerPair(method, typeDescriptor); invocationTarget = new InvokerPair(method, typeDescriptor);
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
this.readerCache.put(cacheKey, invocationTarget); this.readerCache.put(cacheKey, invocationTarget);
@ -561,7 +561,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (invocationTarget == null || invocationTarget.member instanceof Field) { if (invocationTarget == null || invocationTarget.member instanceof Field) {
Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); Field field = (invocationTarget != null ? (Field) invocationTarget.member : null);
if (field == null) { if (field == null) {
field = findField(name, clazz, target instanceof Class); field = findField(name, type, target instanceof Class);
if (field != null) { if (field != null) {
invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); invocationTarget = new InvokerPair(field, new TypeDescriptor(field));
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
@ -600,7 +600,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private final String property; private final String property;
private boolean targetIsClass; private final boolean targetIsClass;
public PropertyCacheKey(Class<?> clazz, String name, boolean targetIsClass) { public PropertyCacheKey(Class<?> clazz, String name, boolean targetIsClass) {
this.clazz = clazz; this.clazz = clazz;

Loading…
Cancel
Save