Skip to content

Commit b25e91a

Browse files
author
Phillip Webb
committed
Relax JavaBean rules for SpEL property access
Relax the method search algorithm used by `ReflectivePropertyAccessor` to include methods of the form `getXY()` for properties of the form `xy`. Although the JavaBean specification indicates that a property `xy` should use the accessors `getxY()` and `setxY()`, in practice many developers choose to have an uppercase first character. The `ReflectivePropertyAccessor` will now consider these style methods if the traditional conventions fail to find a match. Issue: SPR-10716
1 parent 59fcf50 commit b25e91a

File tree

2 files changed

+49
-29
lines changed

2 files changed

+49
-29
lines changed

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

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -318,42 +318,34 @@ private Field findField(String name, Class<?> clazz, Object target) {
318318
* Find a getter method for the specified property.
319319
*/
320320
protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
321-
Method[] ms = getSortedClassMethods(clazz);
322-
String propertyMethodSuffix = getPropertyMethodSuffix(propertyName);
323-
324-
// Try "get*" method...
325-
String getterName = "get" + propertyMethodSuffix;
326-
for (Method method : ms) {
327-
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
328-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
329-
return method;
330-
}
331-
}
332-
// Try "is*" method...
333-
getterName = "is" + propertyMethodSuffix;
334-
for (Method method : ms) {
335-
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
336-
(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())) &&
337-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
338-
return method;
339-
}
340-
}
341-
return null;
321+
return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
322+
new String[] { "get", "is" }, clazz, mustBeStatic, 0);
342323
}
343324

344325
/**
345326
* Find a setter method for the specified property.
346327
*/
347328
protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
329+
return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
330+
new String[] { "set" }, clazz, mustBeStatic, 1);
331+
}
332+
333+
private Method findMethodForProperty(String[] methodSuffixes, String[] prefixes, Class<?> clazz,
334+
boolean mustBeStatic, int numberOfParams) {
348335
Method[] methods = getSortedClassMethods(clazz);
349-
String setterName = "set" + getPropertyMethodSuffix(propertyName);
350-
for (Method method : methods) {
351-
if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 &&
352-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
353-
return method;
336+
for (String methodSuffix : methodSuffixes) {
337+
for (String prefix : prefixes) {
338+
for (Method method : methods) {
339+
if (method.getName().equals(prefix + methodSuffix)
340+
&& method.getParameterTypes().length == numberOfParams
341+
&& (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
342+
return method;
343+
}
344+
}
354345
}
355346
}
356347
return null;
348+
357349
}
358350

359351
/**
@@ -370,13 +362,29 @@ public int compare(Method o1, Method o2) {
370362
return methods;
371363
}
372364

365+
/**
366+
* Return the method suffixes for a given property name. The default implementation
367+
* uses JavaBean conventions with additional support for properties of the form 'xY'
368+
* where the method 'getXY()' is used in preference to the JavaBean convention of
369+
* 'getxY()'.
370+
*/
371+
protected String[] getPropertyMethodSuffixes(String propertyName) {
372+
String suffix = getPropertyMethodSuffix(propertyName);
373+
if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) {
374+
return new String[] { suffix };
375+
}
376+
return new String[] { suffix, StringUtils.capitalize(suffix) };
377+
}
378+
379+
/**
380+
* Return the method suffix for a given property name. The default implementation
381+
* uses JavaBean conventions.
382+
*/
373383
protected String getPropertyMethodSuffix(String propertyName) {
374384
if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) {
375385
return propertyName;
376386
}
377-
else {
378-
return StringUtils.capitalize(propertyName);
379-
}
387+
return StringUtils.capitalize(propertyName);
380388
}
381389

382390
/**

spring-expression/src/test/java/org/springframework/expression/spel/support/ReflectionHelperTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,12 @@ public void testReflectivePropertyResolver() throws Exception {
335335
assertEquals("id",rpr.read(ctx,t,"Id").getValue());
336336
assertTrue(rpr.canRead(ctx,t,"Id"));
337337

338+
// repro SPR-10994
339+
assertEquals("xyZ",rpr.read(ctx,t,"xyZ").getValue());
340+
assertTrue(rpr.canRead(ctx,t,"xyZ"));
341+
assertEquals("xY",rpr.read(ctx,t,"xY").getValue());
342+
assertTrue(rpr.canRead(ctx,t,"xY"));
343+
338344
// SPR-10122, ReflectivePropertyAccessor JavaBean property names compliance tests - setters
339345
rpr.write(ctx, t, "pEBS","Test String");
340346
assertEquals("Test String",rpr.read(ctx,t,"pEBS").getValue());
@@ -429,6 +435,8 @@ static class Tester {
429435
String id = "id";
430436
String ID = "ID";
431437
String pEBS = "pEBS";
438+
String xY = "xY";
439+
String xyZ = "xyZ";
432440

433441
public String getProperty() { return property; }
434442
public void setProperty(String value) { property = value; }
@@ -445,6 +453,10 @@ static class Tester {
445453

446454
public String getID() { return ID; }
447455

456+
public String getXY() { return xY; }
457+
458+
public String getXyZ() { return xyZ; }
459+
448460
public String getpEBS() {
449461
return pEBS;
450462
}

0 commit comments

Comments
 (0)