INT-4229: SpEL Customization Via Java Config
JIRA: https://jira.spring.io/browse/INT-4229 * Make `SpelPropertyAccessorRegistrar` as `public` class and provide more API to customize it. This class allows to register `PropertyAccessor` s for shared `EvaluationContext` * Document how to configure SpEL functions and `PropertyAccessor` s with Java Config **cherry-pick to 4.3.x**
This commit is contained in:
committed by
Gary Russell
parent
e872791a69
commit
3bf2ca12ba
@@ -39,12 +39,12 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
|
||||
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
@@ -91,8 +91,7 @@ public class OutboundGatewayTests {
|
||||
}
|
||||
}).when(context).getBean(anyString());
|
||||
when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true);
|
||||
when(context.getBean(ClassUtils.forName("org.springframework.integration.config.SpelPropertyAccessorRegistrar",
|
||||
context.getClassLoader())))
|
||||
when(context.getBean(SpelPropertyAccessorRegistrar.class))
|
||||
.thenThrow(NoSuchBeanDefinitionException.class);
|
||||
IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean =
|
||||
new IntegrationEvaluationContextFactoryBean();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.expression.TypeLocator;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -69,6 +70,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class IntegrationEvaluationContextFactoryBean implements FactoryBean<StandardEvaluationContext>,
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2016 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
|
||||
*
|
||||
* http://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.integration.config;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
|
||||
/**
|
||||
* Utility class that keeps track of a Set of SpEL {@link PropertyAccessor}s
|
||||
* in order to register them with the "integrationEvaluationContext" upon initialization.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 3.0
|
||||
*/
|
||||
class SpelPropertyAccessorRegistrar {
|
||||
|
||||
private final Map<String, PropertyAccessor> propertyAccessors;
|
||||
|
||||
SpelPropertyAccessorRegistrar(Map<String, PropertyAccessor> propertyAccessors) {
|
||||
this.propertyAccessors = propertyAccessors;
|
||||
}
|
||||
|
||||
Map<String, PropertyAccessor> getPropertyAccessors() {
|
||||
return this.propertyAccessors;
|
||||
}
|
||||
|
||||
void addPropertyAccessor(String name, PropertyAccessor propertyAccessor) {
|
||||
this.propertyAccessors.put(name, propertyAccessor);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2017 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.
|
||||
@@ -25,12 +25,12 @@ import org.w3c.dom.NodeList;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.IntegrationConfigUtils;
|
||||
import org.springframework.integration.context.IntegrationContextUtils;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -43,11 +43,9 @@ public class SpelPropertyAccessorsParser implements BeanDefinitionParser {
|
||||
|
||||
private final Map<String, Object> propertyAccessors = new ManagedMap<String, Object>();
|
||||
|
||||
private volatile boolean initialized;
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
this.initializeSpelPropertyAccessorRegistrarIfNecessary(parserContext);
|
||||
initializeSpelPropertyAccessorRegistrarIfNecessary(parserContext);
|
||||
|
||||
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
|
||||
|
||||
@@ -55,9 +53,10 @@ public class SpelPropertyAccessorsParser implements BeanDefinitionParser {
|
||||
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node node = children.item(i);
|
||||
String propertyAccessorName = null;
|
||||
Object propertyAccessor = null;
|
||||
if (node instanceof Element && !delegate.nodeNameEquals(node, BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)) {
|
||||
String propertyAccessorName;
|
||||
Object propertyAccessor;
|
||||
if (node instanceof Element &&
|
||||
!delegate.nodeNameEquals(node, BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)) {
|
||||
Element ele = (Element) node;
|
||||
|
||||
if (delegate.nodeNameEquals(ele, BeanDefinitionParserDelegate.BEAN_ELEMENT)) {
|
||||
@@ -87,14 +86,16 @@ public class SpelPropertyAccessorsParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
private synchronized void initializeSpelPropertyAccessorRegistrarIfNecessary(ParserContext parserContext) {
|
||||
if (!this.initialized) {
|
||||
if (!parserContext.getRegistry()
|
||||
.containsBeanDefinition(IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME)) {
|
||||
|
||||
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(IntegrationConfigUtils.BASE_PACKAGE + ".config.SpelPropertyAccessorRegistrar")
|
||||
.genericBeanDefinition(SpelPropertyAccessorRegistrar.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addConstructorArgValue(this.propertyAccessors);
|
||||
BeanDefinitionReaderUtils.registerWithGeneratedName(registrarBuilder.getBeanDefinition(),
|
||||
parserContext.getRegistry());
|
||||
this.initialized = true;
|
||||
parserContext.getRegistry()
|
||||
.registerBeanDefinition(IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME,
|
||||
registrarBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -81,6 +81,8 @@ public abstract class IntegrationContextUtils {
|
||||
|
||||
public static final String INTEGRATION_GRAPH_SERVER_BEAN_NAME = "integrationGraphServer";
|
||||
|
||||
public static final String SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME = "spelPropertyAccessorRegistrar";
|
||||
|
||||
/**
|
||||
* @param beanFactory BeanFactory for lookup, must not be null.
|
||||
* @return The {@link MetadataStore} bean whose name is "metadataStore".
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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.integration.expression;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class that keeps track of a Set of SpEL {@link PropertyAccessor}s
|
||||
* in order to register them with the "integrationEvaluationContext" upon initialization.
|
||||
* Accessors must be added before context refresh.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SpelPropertyAccessorRegistrar {
|
||||
|
||||
private final Map<String, PropertyAccessor> propertyAccessors = new HashMap<String, PropertyAccessor>();
|
||||
|
||||
public SpelPropertyAccessorRegistrar() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the provided property accessors. Each accessor name
|
||||
* will be the class simple name.
|
||||
* @param propertyAccessors the accessors.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar(PropertyAccessor... propertyAccessors) {
|
||||
Assert.notEmpty(propertyAccessors, "'propertyAccessors' must not be empty");
|
||||
for (PropertyAccessor propertyAccessor : propertyAccessors) {
|
||||
this.propertyAccessors.put(propertyAccessors.getClass().getSimpleName(), propertyAccessor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with the provided named property accessors.
|
||||
* @param propertyAccessors a map of name:accessor.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar(Map<String, PropertyAccessor> propertyAccessors) {
|
||||
Assert.notEmpty(propertyAccessors, "'propertyAccessors' must not be empty");
|
||||
this.propertyAccessors.putAll(propertyAccessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the registered accessors.
|
||||
* @return the map of name:accessor.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
public Map<String, PropertyAccessor> getPropertyAccessors() {
|
||||
return this.propertyAccessors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided named property accessor.
|
||||
* @param name the name.
|
||||
* @param propertyAccessor the accessor.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar add(String name, PropertyAccessor propertyAccessor) {
|
||||
Assert.hasText(name, "'name' must not be empty");
|
||||
Assert.notNull(propertyAccessor, "'propertyAccessor' must not be null");
|
||||
this.propertyAccessors.put(name, propertyAccessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided property accessors. Each accessor name
|
||||
* will be the class simple name.
|
||||
* @param propertyAccessors the accessors.
|
||||
* @since 4.3.8
|
||||
*/
|
||||
public SpelPropertyAccessorRegistrar add(PropertyAccessor... propertyAccessors) {
|
||||
Assert.notEmpty(propertyAccessors, "'propertyAccessors' must not be empty");
|
||||
for (PropertyAccessor propertyAccessor : propertyAccessors) {
|
||||
this.propertyAccessors.put(propertyAccessors.getClass().getSimpleName(), propertyAccessor);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.configuration;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -70,6 +71,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.serializer.support.SerializingConverter;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.BridgeFrom;
|
||||
import org.springframework.integration.annotation.BridgeTo;
|
||||
@@ -94,14 +96,17 @@ import org.springframework.integration.config.EnablePublisher;
|
||||
import org.springframework.integration.config.ExpressionControlBusFactoryBean;
|
||||
import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||
import org.springframework.integration.config.IntegrationConverter;
|
||||
import org.springframework.integration.config.SpelFunctionFactoryBean;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
|
||||
import org.springframework.integration.endpoint.PollingConsumer;
|
||||
import org.springframework.integration.expression.SpelPropertyAccessorRegistrar;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.history.MessageHistoryConfigurer;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor;
|
||||
import org.springframework.integration.scheduling.PollerMetadata;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.support.MutableMessageBuilder;
|
||||
@@ -128,6 +133,7 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.support.AnnotationConfigContextLoader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import reactor.rx.Promise;
|
||||
@@ -684,6 +690,16 @@ public class EnableIntegrationTests {
|
||||
this.autoCreatedChannelMessageSourceAdapter.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntegrationEvaluationContextCustomization() {
|
||||
EvaluationContext evaluationContext = this.context.getBean(EvaluationContext.class);
|
||||
List<?> propertyAccessors = TestUtils.getPropertyValue(evaluationContext, "propertyAccessors", List.class);
|
||||
assertThat(propertyAccessors.get(0), instanceOf(JsonPropertyAccessor.class));
|
||||
Map<?, ?> variables = TestUtils.getPropertyValue(evaluationContext, "variables", Map.class);
|
||||
Object testSpelFunction = variables.get("testSpelFunction");
|
||||
assertEquals(ClassUtils.getStaticMethod(TestSpelFunction.class, "bar", Object.class), testSpelFunction);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@IntegrationComponentScan
|
||||
@@ -1086,6 +1102,16 @@ public class EnableIntegrationTests {
|
||||
return new AnnotationTestServiceImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpelFunctionFactoryBean testSpelFunction() {
|
||||
return new SpelFunctionFactoryBean(TestSpelFunction.class, "bar");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpelPropertyAccessorRegistrar spelPropertyAccessorRegistrar() {
|
||||
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -1589,4 +1615,13 @@ public class EnableIntegrationTests {
|
||||
// @MessagingGateway(defaultRequestChannel = "gatewayChannel", defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))
|
||||
// public static class TestGateway2 { }
|
||||
|
||||
|
||||
public static class TestSpelFunction {
|
||||
|
||||
public static Object bar(Object o) {
|
||||
return o;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,6 +104,17 @@ With this sample:
|
||||
|
||||
* That `EvaluationContext` instance is injected into the `ExpressionEvaluatingTransformer` bean.
|
||||
|
||||
To provide a SpEL Function via Java Configuration you should declare a `SpelFunctionFactoryBean` bean for each function.
|
||||
The sample above can be configured as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SpelFunctionFactoryBean xpath() {
|
||||
return new SpelFunctionFactoryBean(XPathUtils.class, "evaluate");
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: SpEL functions declared in a parent context are also made available in any child context(s).
|
||||
Each context has its own instance of the _integrationEvaluationContext_ factory bean because each needs a different `BeanResolver`, but the function declarations are inherited and can be overridden if needed by declaring a SpEL function with the same name.
|
||||
|
||||
@@ -157,6 +168,18 @@ Instead of configuring the factory bean above, simply add one or more of these c
|
||||
|
||||
With this sample, two custom `PropertyAccessor` s will be injected to the `EvaluationContext` in the order that they are declared.
|
||||
|
||||
To provide `PropertyAccessor` s via Java Configuration you should declare `SpelPropertyAccessorRegistrar` bean with the `spelPropertyAccessorRegistrar` (the `IntegrationContextUtils.SPEL_PROPERTY_ACCESSOR_REGISTRAR_BEAN_NAME` constant) name.
|
||||
The sample above can be configured like:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public SpelPropertyAccessorRegistrar spelPropertyAccessorRegistrar() {
|
||||
return new SpelPropertyAccessorRegistrar(new JsonPropertyAccessor())
|
||||
.add(fooPropertyAccessor());
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: Custom `PropertyAccessor` s declared in a parent context are also made available in any child context(s).
|
||||
They are placed at the end of result list (but before the default `org.springframework.context.expression.MapAccessor` and `o.s.expression.spel.support.ReflectivePropertyAccessor`).
|
||||
If a `PropertyAccessor` with the same bean id is declared in a child context(s), it will override the parent accessor.
|
||||
|
||||
Reference in New Issue
Block a user