From a1eae42fd08243e727790734e93c3fe4cc5e4ff7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 5 Jul 2019 17:07:44 +0200 Subject: [PATCH] Expose implementation method for annotation introspection purposes Closes gh-23210 --- .../support/ReflectivePropertyAccessor.java | 32 +++++++++------ .../expression/spel/SpelReproTests.java | 41 +++++++++++++++---- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 3ef76790e9b0..99c763da52b1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -138,6 +138,7 @@ public boolean canRead(EvaluationContext context, @Nullable Object target, Strin // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -180,6 +181,7 @@ public TypedValue read(EvaluationContext context, @Nullable Object target, Strin // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); invoker = new InvokerPair(method, typeDescriptor); this.lastReadInvokerPair = invoker; this.readerCache.put(cacheKey, invoker); @@ -239,6 +241,7 @@ public boolean canWrite(EvaluationContext context, @Nullable Object target, Stri // Treat it like a property Property property = new Property(type, null, method); TypeDescriptor typeDescriptor = new TypeDescriptor(property); + method = ClassUtils.getInterfaceMethodIfPossible(method); this.writerCache.put(cacheKey, method); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -287,6 +290,7 @@ public void write(EvaluationContext context, @Nullable Object target, String nam if (method == null) { method = findSetterForProperty(name, type, target); if (method != null) { + method = ClassUtils.getInterfaceMethodIfPossible(method); cachedMember = method; this.writerCache.put(cacheKey, cachedMember); } @@ -414,13 +418,24 @@ private Method findMethodForProperty(String[] methodSuffixes, String prefix, Cla method.getParameterCount() == numberOfParams && (!mustBeStatic || Modifier.isStatic(method.getModifiers())) && (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) { - return ClassUtils.getInterfaceMethodIfPossible(method); + return method; } } } return null; } + /** + * Return class methods ordered with non-bridge methods appearing higher. + */ + private Method[] getSortedMethods(Class clazz) { + return this.sortedMethodsCache.computeIfAbsent(clazz, key -> { + Method[] methods = key.getMethods(); + Arrays.sort(methods, (o1, o2) -> (o1.isBridge() == o2.isBridge() ? 0 : (o1.isBridge() ? 1 : -1))); + return methods; + }); + } + /** * Determine whether the given {@code Method} is a candidate for property access * on an instance of the given target class. @@ -434,17 +449,6 @@ protected boolean isCandidateForProperty(Method method, Class targetClass) { return true; } - /** - * Return class methods ordered with non-bridge methods appearing higher. - */ - private Method[] getSortedMethods(Class clazz) { - return this.sortedMethodsCache.computeIfAbsent(clazz, key -> { - Method[] methods = key.getMethods(); - Arrays.sort(methods, (o1, o2) -> (o1.isBridge() == o2.isBridge() ? 0 : (o1.isBridge() ? 1 : -1))); - return methods; - }); - } - /** * Return the method suffixes for a given property name. The default implementation * uses JavaBean conventions with additional support for properties of the form 'xY' @@ -536,7 +540,9 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext context, @Nullab if (method == null) { method = findGetterForProperty(name, clazz, target); if (method != null) { - invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1))); + TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1)); + method = ClassUtils.getInterfaceMethodIfPossible(method); + invocationTarget = new InvokerPair(method, typeDescriptor); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index eba067e0c1ef..e37867e54654 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,10 +60,18 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver; +import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Reproduction tests cornering various reported SpEL issues. @@ -1213,9 +1221,13 @@ public void SPR9486_floatPowerDouble() { public void SPR9994_bridgeMethods() throws Exception { ReflectivePropertyAccessor accessor = new ReflectivePropertyAccessor(); StandardEvaluationContext context = new StandardEvaluationContext(); - Object target = new GenericImplementation(); + GenericImplementation target = new GenericImplementation(); + accessor.write(context, target, "property", "1"); + assertEquals(1, target.value); TypedValue value = accessor.read(context, target, "property"); + assertEquals(1, value.getValue()); assertEquals(Integer.class, value.getTypeDescriptor().getType()); + assertTrue(value.getTypeDescriptor().getAnnotations().length > 0); } @Test @@ -1224,6 +1236,7 @@ public void SPR10162_onlyBridgeMethod() throws Exception { StandardEvaluationContext context = new StandardEvaluationContext(); Object target = new OnlyBridgeMethod(); TypedValue value = accessor.read(context, target, "property"); + assertNull(value.getValue()); assertEquals(Integer.class, value.getTypeDescriptor().getType()); } @@ -1232,7 +1245,7 @@ public void SPR10091_simpleTestValueType() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Class valueType = parser.parseExpression("simpleProperty").getValueType(evaluationContext); - assertNotNull(valueType); + assertEquals(Boolean.class, valueType); } @Test @@ -1240,7 +1253,7 @@ public void SPR10091_simpleTestValue() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Object value = parser.parseExpression("simpleProperty").getValue(evaluationContext); - assertNotNull(value); + assertEquals(Boolean.class, value.getClass()); } @Test @@ -1248,7 +1261,7 @@ public void SPR10091_primitiveTestValueType() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Class valueType = parser.parseExpression("primitiveProperty").getValueType(evaluationContext); - assertNotNull(valueType); + assertEquals(Boolean.class, valueType); } @Test @@ -1256,7 +1269,7 @@ public void SPR10091_primitiveTestValue() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(new BooleanHolder()); Object value = parser.parseExpression("primitiveProperty").getValue(evaluationContext); - assertNotNull(value); + assertEquals(Boolean.class, value.getClass()); } @Test @@ -2220,15 +2233,25 @@ public boolean isPrimitiveProperty() { private interface GenericInterface { + void setProperty(T value); + T getProperty(); } private static class GenericImplementation implements GenericInterface { + int value; + + @Override + public void setProperty(Integer value) { + this.value = value; + } + @Override + @Nullable public Integer getProperty() { - return null; + return this.value; } }