|
| 1 | +[[valueexpressions.fundamentals]] |
| 2 | += Value Expressions Fundamentals |
| 3 | + |
| 4 | +Value Expressions are a combination of {spring-framework-docs}/core/expressions.html[Spring Expression Language (SpEL)] and {spring-framework-docs}/core/beans/environment.html#beans-placeholder-resolution-in-statements[Property Placeholder Resolution]. |
| 5 | +They combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the `Environment` such as configuration properties. |
| 6 | + |
| 7 | +Expressions are expected to be defined by a trusted input such as an annotation value and not to be determined from user input. |
| 8 | + |
| 9 | +The following code demonstrates how to use expressions in the context of annotations. |
| 10 | + |
| 11 | +.Annotation Usage |
| 12 | +==== |
| 13 | +[source,java] |
| 14 | +---- |
| 15 | +@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}") |
| 16 | +class Order { |
| 17 | + // … |
| 18 | +} |
| 19 | +---- |
| 20 | +==== |
| 21 | + |
| 22 | +Value Expressions can be defined from a sole SpEL Expression, a Property Placeholder or a composite expression mixing various expressions including literals. |
| 23 | + |
| 24 | +.Expression Examples |
| 25 | +==== |
| 26 | +[source] |
| 27 | +---- |
| 28 | +#{tenantService.getOrderCollection()} <1> |
| 29 | +#{(1+1) + '-hello-world'} <2> |
| 30 | +${tenant-config.suffix} <3> |
| 31 | +orders-${tenant-config.suffix} <4> |
| 32 | +#{tenantService.getOrderCollection()}-${tenant-config.suffix} <5> |
| 33 | +---- |
| 34 | +
|
| 35 | +<1> Value Expression using a single SpEL Expression. |
| 36 | +<2> Value Expression using a static SpEL Expression evaluating to `2-hello-world`. |
| 37 | +<3> Value Expression using a single Property Placeholder. |
| 38 | +<4> Composite expression comprised of the literal `orders-` and the Property Placeholder `${tenant-config.suffix}`. |
| 39 | +<5> Composite expression using SpEL, Property Placeholders and literals. |
| 40 | +==== |
| 41 | + |
| 42 | +NOTE: Using value expressions introduces a lot of flexibility to your code. |
| 43 | +Doing so requires evaluation of the expression on each usage and, therefore, value expression evaluation has an impact on the performance profile. |
| 44 | + |
| 45 | +[[valueexpressions.api]] |
| 46 | +== Parsing and Evaluation |
| 47 | + |
| 48 | +Value Expressions are parsed by the `ValueExpressionParser` API. |
| 49 | +Instances of `ValueExpression` are thread-safe and can be cached for later use to avoid repeated parsing. |
| 50 | + |
| 51 | +The following example shows the Value Expression API usage: |
| 52 | + |
| 53 | +.Parsing and Evaluation |
| 54 | +[tabs] |
| 55 | +====== |
| 56 | +Java:: |
| 57 | ++ |
| 58 | +[source,java,role="primary"] |
| 59 | +---- |
| 60 | +ValueParserConfiguration configuration = SpelExpressionParser::new; |
| 61 | +ValueEvaluationContext context = ValueEvaluationContext.of(environment, evaluationContext); |
| 62 | +
|
| 63 | +ValueExpressionParser parser = ValueExpressionParser.create(configuration); |
| 64 | +ValueExpression expression = parser.parse("Hello, World"); |
| 65 | +Object result = expression.evaluate(context); |
| 66 | +---- |
| 67 | +
|
| 68 | +Kotlin:: |
| 69 | ++ |
| 70 | +[source,kotlin,role="secondary"] |
| 71 | +---- |
| 72 | +val configuration = ValueParserConfiguration { SpelExpressionParser() } |
| 73 | +val context = ValueEvaluationContext.of(environment, evaluationContext) |
| 74 | +
|
| 75 | +val parser = ValueExpressionParser.create(configuration) |
| 76 | +val expression: ValueExpression = parser.parse("Hello, World") |
| 77 | +val result: Any = expression.evaluate(context) |
| 78 | +---- |
| 79 | +====== |
| 80 | + |
| 81 | +[[valueexpressions.spel]] |
| 82 | +== SpEL Expressions |
| 83 | + |
| 84 | +{spring-framework-docs}/core/expressions.html[SpEL Expressions] follow the Template style where the expression is expected to be enclosed within the `#{…}` format. |
| 85 | +Expressions are evaluated using an `EvaluationContext` that is provided by `EvaluationContextProvider`. |
| 86 | +The context itself is a powerful `StandardEvaluationContext` allowing a wide range of operations, access to static types and context extensions. |
| 87 | + |
| 88 | +NOTE: Make sure to parse and evaluate only expressions from trusted sources such as annotations. |
| 89 | +Accepting user-provided expressions can create an entry path to exploit the application context and your system resulting in a potential security vulnerability. |
| 90 | + |
| 91 | +=== Extending the Evaluation Context |
| 92 | + |
| 93 | +`EvaluationContextProvider` and its reactive variant `ReactiveEvaluationContextProvider` provide access to an `EvaluationContext`. |
| 94 | +`ExtensionAwareEvaluationContextProvider` and its reactive variant `ReactiveExtensionAwareEvaluationContextProvider` are default implementations that determine context extensions from an application context, specifically `ListableBeanFactory`. |
| 95 | + |
| 96 | +Extensions implement either `EvaluationContextExtension` or `ReactiveEvaluationContextExtension` to provide extension support to hydrate `EvaluationContext`. |
| 97 | +That are a root object, properties and functions (top-level methods). |
| 98 | + |
| 99 | +The following example shows a context extension that provides a root object, properties, functions and an aliased function. |
| 100 | + |
| 101 | +.Implementing a `EvaluationContextExtension` |
| 102 | +[tabs] |
| 103 | +====== |
| 104 | +Java:: |
| 105 | ++ |
| 106 | +[source,java,role="primary"] |
| 107 | +---- |
| 108 | +@Component |
| 109 | +public class MyExtension implements EvaluationContextExtension { |
| 110 | +
|
| 111 | + @Override |
| 112 | + public String getExtensionId() { |
| 113 | + return "my-extension"; |
| 114 | + } |
| 115 | +
|
| 116 | + @Override |
| 117 | + public Object getRootObject() { |
| 118 | + return new CustomExtensionRootObject(); |
| 119 | + } |
| 120 | +
|
| 121 | + @Override |
| 122 | + public Map<String, Object> getProperties() { |
| 123 | +
|
| 124 | + Map<String, Object> properties = new HashMap<>(); |
| 125 | +
|
| 126 | + properties.put("key", "Hello"); |
| 127 | +
|
| 128 | + return properties; |
| 129 | + } |
| 130 | +
|
| 131 | + @Override |
| 132 | + public Map<String, Function> getFunctions() { |
| 133 | +
|
| 134 | + Map<String, Function> functions = new HashMap<>(); |
| 135 | +
|
| 136 | + try { |
| 137 | + functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod"))); |
| 138 | + return functions; |
| 139 | + } catch (Exception o_O) { |
| 140 | + throw new RuntimeException(o_O); |
| 141 | + } |
| 142 | + } |
| 143 | +
|
| 144 | + public static String extensionMethod() { |
| 145 | + return "Hello World"; |
| 146 | + } |
| 147 | +
|
| 148 | + public static int add(int i1, int i2) { |
| 149 | + return i1 + i2; |
| 150 | + } |
| 151 | +
|
| 152 | +} |
| 153 | +
|
| 154 | +public class CustomExtensionRootObject { |
| 155 | +
|
| 156 | + public boolean rootObjectInstanceMethod() { |
| 157 | + return true; |
| 158 | + } |
| 159 | +
|
| 160 | +} |
| 161 | +---- |
| 162 | +
|
| 163 | +Kotlin:: |
| 164 | ++ |
| 165 | +[source,kotlin,role="secondary"] |
| 166 | +---- |
| 167 | +@Component |
| 168 | +class MyExtension : EvaluationContextExtension { |
| 169 | +
|
| 170 | + override fun getExtensionId(): String { |
| 171 | + return "my-extension" |
| 172 | + } |
| 173 | +
|
| 174 | + override fun getRootObject(): Any? { |
| 175 | + return CustomExtensionRootObject() |
| 176 | + } |
| 177 | +
|
| 178 | + override fun getProperties(): Map<String, Any> { |
| 179 | + val properties: MutableMap<String, Any> = HashMap() |
| 180 | +
|
| 181 | + properties["key"] = "Hello" |
| 182 | +
|
| 183 | + return properties |
| 184 | + } |
| 185 | +
|
| 186 | + override fun getFunctions(): Map<String, Function> { |
| 187 | + val functions: MutableMap<String, Function> = HashMap() |
| 188 | +
|
| 189 | + try { |
| 190 | + functions["aliasedMethod"] = Function(javaClass.getMethod("extensionMethod")) |
| 191 | + return functions |
| 192 | + } catch (o_O: Exception) { |
| 193 | + throw RuntimeException(o_O) |
| 194 | + } |
| 195 | + } |
| 196 | +
|
| 197 | + companion object { |
| 198 | + fun extensionMethod(): String { |
| 199 | + return "Hello World" |
| 200 | + } |
| 201 | +
|
| 202 | + fun add(i1: Int, i2: Int): Int { |
| 203 | + return i1 + i2 |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | +
|
| 208 | +class CustomExtensionRootObject { |
| 209 | + fun rootObjectInstanceMethod(): Boolean { |
| 210 | + return true |
| 211 | + } |
| 212 | +} |
| 213 | +---- |
| 214 | +====== |
| 215 | + |
| 216 | +Once the above shown extension is registered, you can use its exported methods, properties and root object to evaluate SpEL expressions: |
| 217 | + |
| 218 | +.Expression Evaluation Examples |
| 219 | +==== |
| 220 | +[source] |
| 221 | +---- |
| 222 | +#{add(1, 2)} <1> |
| 223 | +#{extensionMethod()} <2> |
| 224 | +#{aliasedMethod()} <3> |
| 225 | +#{key} <4> |
| 226 | +#{rootObjectInstanceMethod()} <5> |
| 227 | +---- |
| 228 | +
|
| 229 | +<1> Invoke the method `add` declared by `MyExtension` resulting in `3` as the method adds both numeric parameters and returns the sum. |
| 230 | +<2> Invoke the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`. |
| 231 | +<3> Invoke the method `aliasedMethod`. |
| 232 | +The method is exposed as function and redirects into the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`. |
| 233 | +<4> Evaluate the `key` property resulting in `Hello`. |
| 234 | +<5> Invoke the method `rootObjectInstanceMethod` on the root object instance `CustomExtensionRootObject`. |
| 235 | +==== |
| 236 | + |
| 237 | +You can find real-life context extensions at https://github.com/spring-projects/spring-security/blob/main/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java[`SecurityEvaluationContextExtension`]. |
| 238 | + |
| 239 | +[[valueexpressions.property-placeholders]] |
| 240 | +== Property Placeholders |
| 241 | + |
| 242 | +Property placeholders following the form `${…}` refer to properties provided typically by a `PropertySource` through `Environment`. |
| 243 | +Properties are useful to resolve against system properties, application configuration files, environment configuration or property sources contributed by secret management systems. |
| 244 | +You can find more details on the property placeholders in {spring-framework-docs}/core/beans/annotation-config/value-annotations.html#page-title[Spring Framework's documentation on `@Value` usage]. |
| 245 | + |
| 246 | + |
0 commit comments