Support SpEL compilation for public methods in private subtypes
Although the Spring Expression Language (SpEL) generally does a good job of locating the public declaring class or interface on which to invoke a method in a compiled expression, prior to this commit there were still a few unsupported use cases. To address those remaining use cases, this commit ensures that methods are invoked via a public interface or public superclass whenever possible when compiling SpEL expressions. See gh-29857
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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;
|
||||
|
||||
/**
|
||||
* This is intentionally a top-level public interface.
|
||||
*/
|
||||
public interface PublicInterface {
|
||||
|
||||
String getText();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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;
|
||||
|
||||
/**
|
||||
* This is intentionally a top-level public class.
|
||||
*/
|
||||
public class PublicSuperclass {
|
||||
|
||||
public int process(int num) {
|
||||
return num + 1;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return "goodbye";
|
||||
}
|
||||
|
||||
public String greet(String name) {
|
||||
return "Super, " + name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.ast.CompoundExpression;
|
||||
import org.springframework.expression.spel.ast.InlineList;
|
||||
import org.springframework.expression.spel.ast.OpLT;
|
||||
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
||||
import org.springframework.expression.spel.ast.Ternary;
|
||||
@@ -678,6 +679,158 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PropertyVisibilityTests {
|
||||
|
||||
@Test
|
||||
void privateSubclassOverridesPropertyInPublicInterface() {
|
||||
expression = parser.parseExpression("text");
|
||||
PrivateSubclass privateSubclass = new PrivateSubclass();
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(privateSubclass.getClass());
|
||||
|
||||
String result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("enigma");
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("enigma");
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateSubclassOverridesPropertyInPrivateInterface() {
|
||||
expression = parser.parseExpression("message");
|
||||
PrivateSubclass privateSubclass = new PrivateSubclass();
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(privateSubclass.getClass());
|
||||
|
||||
String result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("hello");
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateSubclassOverridesPropertyInPublicSuperclass() {
|
||||
expression = parser.parseExpression("number");
|
||||
PrivateSubclass privateSubclass = new PrivateSubclass();
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(privateSubclass.getClass());
|
||||
|
||||
Integer result = expression.getValue(context, privateSubclass, Integer.class);
|
||||
assertThat(result).isEqualTo(2);
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, privateSubclass, Integer.class);
|
||||
assertThat(result).isEqualTo(2);
|
||||
}
|
||||
|
||||
private interface PrivateInterface {
|
||||
|
||||
String getMessage();
|
||||
}
|
||||
|
||||
private static class PrivateSubclass extends PublicSuperclass implements PublicInterface, PrivateInterface {
|
||||
|
||||
@Override
|
||||
public int getNumber() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "enigma";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MethodVisibilityTests {
|
||||
|
||||
/**
|
||||
* Note that {@link InlineList} creates a list and wraps it via
|
||||
* {@link Collections#unmodifiableList(List)}, whose concrete type is
|
||||
* package private.
|
||||
*/
|
||||
@Test
|
||||
void packagePrivateSubclassOverridesMethodInPublicInterface() {
|
||||
expression = parser.parseExpression("{2021, 2022}");
|
||||
List<?> inlineList = expression.getValue(List.class);
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(inlineList.getClass());
|
||||
|
||||
expression = parser.parseExpression("{2021, 2022}.contains(2022)");
|
||||
Boolean result = expression.getValue(context, Boolean.class);
|
||||
assertThat(result).isTrue();
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, Boolean.class);
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void packagePrivateSubclassOverridesMethodInPrivateInterface() {
|
||||
expression = parser.parseExpression("greet('Jane')");
|
||||
PrivateSubclass privateSubclass = new PrivateSubclass();
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(privateSubclass.getClass());
|
||||
|
||||
String result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("Hello, Jane");
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, privateSubclass, String.class);
|
||||
assertThat(result).isEqualTo("Hello, Jane");
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateSubclassOverridesMethodInPublicSuperclass() {
|
||||
expression = parser.parseExpression("process(2)");
|
||||
PrivateSubclass privateSubclass = new PrivateSubclass();
|
||||
|
||||
// Prerequisite: type must not be public for this use case.
|
||||
assertNotPublic(privateSubclass.getClass());
|
||||
|
||||
Integer result = expression.getValue(context, privateSubclass, Integer.class);
|
||||
assertThat(result).isEqualTo(2 * 2);
|
||||
|
||||
assertCanCompile(expression);
|
||||
result = expression.getValue(context, privateSubclass, Integer.class);
|
||||
assertThat(result).isEqualTo(2 * 2);
|
||||
}
|
||||
|
||||
private interface PrivateInterface {
|
||||
|
||||
String greet(String name);
|
||||
}
|
||||
|
||||
private static class PrivateSubclass extends PublicSuperclass implements PrivateInterface {
|
||||
|
||||
@Override
|
||||
public int process(int num) {
|
||||
return num * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String greet(String name) {
|
||||
return "Hello, " + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void typeReference() {
|
||||
expression = parse("T(String)");
|
||||
|
||||
Reference in New Issue
Block a user