SpEL supports record-style accessor methods as well

Closes gh-26029
This commit is contained in:
Juergen Hoeller 2020-11-04 16:51:54 +01:00
parent 412aa06d86
commit 079ca80854
5 changed files with 95 additions and 37 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -118,7 +118,7 @@ public final class Property {
}
// package private
// Package private
MethodParameter getMethodParameter() {
return this.methodParameter;
@ -132,7 +132,7 @@ public final class Property {
}
// internal helpers
// Internal helpers
private String resolveName() {
if (this.readMethod != null) {
@ -142,10 +142,13 @@ public final class Property {
}
else {
index = this.readMethod.getName().indexOf("is");
if (index == -1) {
throw new IllegalArgumentException("Not a getter method");
if (index != -1) {
index += 2;
}
else {
// Record-style plain accessor method, e.g. name()
index = 0;
}
index += 2;
}
return StringUtils.uncapitalize(this.readMethod.getName().substring(index));
}

View File

@ -395,6 +395,11 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (method == null) {
method = findMethodForProperty(getPropertyMethodSuffixes(propertyName),
"is", clazz, mustBeStatic, 0, BOOLEAN_TYPES);
if (method == null) {
// Record-style plain accessor method, e.g. name()
method = findMethodForProperty(new String[] {propertyName},
"", clazz, mustBeStatic, 0, ANY_TYPES);
}
}
return method;
}
@ -683,12 +688,11 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return true;
}
getterName = "is" + StringUtils.capitalize(name);
return getterName.equals(method.getName());
}
else {
Field field = (Field) this.member;
return field.getName().equals(name);
if (getterName.equals(method.getName())) {
return true;
}
}
return this.member.getName().equals(name);
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -36,6 +36,7 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.testresources.Inventor;
import org.springframework.expression.spel.testresources.Person;
import org.springframework.expression.spel.testresources.RecordPerson;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -191,6 +192,20 @@ public class PropertyAccessTests extends AbstractExpressionTests {
parser.parseExpression("name='p3'").getValue(context, target));
}
@Test
public void propertyReadOnlyWithRecordStyle() {
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression expr = parser.parseExpression("name");
RecordPerson target1 = new RecordPerson("p1");
assertThat(expr.getValue(context, target1)).isEqualTo("p1");
RecordPerson target2 = new RecordPerson("p2");
assertThat(expr.getValue(context, target2)).isEqualTo("p2");
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() ->
parser.parseExpression("name='p3'").getValue(context, target2));
}
@Test
public void propertyReadWrite() {
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

View File

@ -4180,6 +4180,13 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(expression.getValue(tc)).isEqualTo("value4");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value4");
// record-style accessor
expression = parser.parseExpression("strawberry");
assertCantCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value5");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value5");
}
@Test
@ -4553,23 +4560,9 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
Object v = expression.getValue(ctx,holder);
assertThat(v).isEqualTo("abc");
// // time it interpreted
// long stime = System.currentTimeMillis();
// for (int i = 0; i < 100000; i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis() - stime));
assertCanCompile(expression);
v = expression.getValue(ctx,holder);
assertThat(v).isEqualTo("abc");
// // time it compiled
// stime = System.currentTimeMillis();
// for (int i = 0; i < 100000; i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis() - stime));
}
@Test
@ -4985,13 +4978,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(fast.compileExpression()).isTrue();
r.setValue2(null);
// try the numbers 0,1,2,null
for (int i=0;i<4;i++) {
r.setValue(i<3?i:null);
for (int i = 0; i < 4; i++) {
r.setValue(i < 3 ? i : null);
boolean slowResult = (Boolean)slow.getValue(ctx);
boolean fastResult = (Boolean)fast.getValue(ctx);
// System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult);
assertThat(fastResult).as(" Differing results: expression="+expressionText+
" value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult);
assertThat(fastResult).as("Differing results: expression=" + expressionText +
" value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult);
}
}
@ -5002,13 +4994,12 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(fast.compileExpression()).isTrue();
Reg r = (Reg)ctx.getRootObject().getValue();
// try the numbers 0,1,2,null
for (int i=0;i<4;i++) {
r.setValue(i<3?i:null);
for (int i = 0; i < 4; i++) {
r.setValue(i < 3 ? i : null);
boolean slowResult = (Boolean)slow.getValue(ctx);
boolean fastResult = (Boolean)fast.getValue(ctx);
// System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult);
assertThat(fastResult).as(" Differing results: expression="+expressionText+
" value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult);
assertThat(fastResult).as("Differing results: expression=" + expressionText +
" value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult);
}
}
@ -5839,7 +5830,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public String orange = "value1";
public static String apple = "value2";
public long peach = 34L;
public String getBanana() {
@ -5849,6 +5839,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public static String getPlum() {
return "value4";
}
public String strawberry() {
return "value5";
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel.testresources;
public class RecordPerson {
private String name;
private Company company;
public RecordPerson(String name) {
this.name = name;
}
public RecordPerson(String name, Company company) {
this.name = name;
this.company = company;
}
public String name() {
return name;
}
public Company company() {
return company;
}
}