Skip to content

Commit f9f1069

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 (cherry picked from commit b25e91a)
1 parent bcf7aec commit f9f1069

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
@@ -314,42 +314,34 @@ private Field findField(String name, Class<?> clazz, Object target) {
314314
* Find a getter method for the specified property.
315315
*/
316316
protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
317-
Method[] ms = getSortedClassMethods(clazz);
318-
String propertyMethodSuffix = getPropertyMethodSuffix(propertyName);
319-
320-
// Try "get*" method...
321-
String getterName = "get" + propertyMethodSuffix;
322-
for (Method method : ms) {
323-
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
324-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
325-
return method;
326-
}
327-
}
328-
// Try "is*" method...
329-
getterName = "is" + propertyMethodSuffix;
330-
for (Method method : ms) {
331-
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
332-
(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())) &&
333-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
334-
return method;
335-
}
336-
}
337-
return null;
317+
return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
318+
new String[] { "get", "is" }, clazz, mustBeStatic, 0);
338319
}
339320

340321
/**
341322
* Find a setter method for the specified property.
342323
*/
343324
protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
325+
return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
326+
new String[] { "set" }, clazz, mustBeStatic, 1);
327+
}
328+
329+
private Method findMethodForProperty(String[] methodSuffixes, String[] prefixes, Class<?> clazz,
330+
boolean mustBeStatic, int numberOfParams) {
344331
Method[] methods = getSortedClassMethods(clazz);
345-
String setterName = "set" + getPropertyMethodSuffix(propertyName);
346-
for (Method method : methods) {
347-
if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 &&
348-
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
349-
return method;
332+
for (String methodSuffix : methodSuffixes) {
333+
for (String prefix : prefixes) {
334+
for (Method method : methods) {
335+
if (method.getName().equals(prefix + methodSuffix)
336+
&& method.getParameterTypes().length == numberOfParams
337+
&& (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
338+
return method;
339+
}
340+
}
350341
}
351342
}
352343
return null;
344+
353345
}
354346

355347
/**
@@ -365,13 +357,29 @@ public int compare(Method o1, Method o2) {
365357
return methods;
366358
}
367359

360+
/**
361+
* Return the method suffixes for a given property name. The default implementation
362+
* uses JavaBean conventions with additional support for properties of the form 'xY'
363+
* where the method 'getXY()' is used in preference to the JavaBean convention of
364+
* 'getxY()'.
365+
*/
366+
protected String[] getPropertyMethodSuffixes(String propertyName) {
367+
String suffix = getPropertyMethodSuffix(propertyName);
368+
if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) {
369+
return new String[] { suffix };
370+
}
371+
return new String[] { suffix, StringUtils.capitalize(suffix) };
372+
}
373+
374+
/**
375+
* Return the method suffix for a given property name. The default implementation
376+
* uses JavaBean conventions.
377+
*/
368378
protected String getPropertyMethodSuffix(String propertyName) {
369379
if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) {
370380
return propertyName;
371381
}
372-
else {
373-
return StringUtils.capitalize(propertyName);
374-
}
382+
return StringUtils.capitalize(propertyName);
375383
}
376384

377385
/**

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)