INT-1639: Add <spel-function> support

* add `<spel-function>` XSD element
* add `SpelFunctionParser`
* add `SpelFunctionRegistrar` to avoid introducing some confused 'Method'-bean
* add `SpelFunctionRegistrar` collaboration with `IntegrationEvaluationContextFactoryBean`
* some refactoring for `IntegrationEvaluationContextFactoryBean`
* polishing some failed tests after this change

JIRA: https://jira.springsource.org/browse/INT-1639

INT-1639: SpelFunctionParser use BeanClassLoader

INT-1639: Inherit 'functions' from parent AC

INT-1639: Document the <spel-function>

INT-1639: 'NoSuchBeanDefinitionException' fix

Polishing
This commit is contained in:
Artem Bilan
2013-05-27 12:49:35 +03:00
committed by Gary Russell
parent 1705a70def
commit 93f61160e2
13 changed files with 420 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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.
@@ -44,6 +44,8 @@ import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Mark Fisher
* @author Dave Syer
* @author Gary Russell
*
* @since 2.1
*/
public class OutboundGatewayTests {
@@ -75,7 +77,7 @@ public class OutboundGatewayTests {
}).when(context).getBean(anyString());
when(context.containsBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME)).thenReturn(true);
IntegrationEvaluationContextFactoryBean integrationEvaluationContextFactoryBean = new IntegrationEvaluationContextFactoryBean();
integrationEvaluationContextFactoryBean.setApplicationContext(context);
integrationEvaluationContextFactoryBean.setBeanFactory(context);
integrationEvaluationContextFactoryBean.afterPropertiesSet();
StandardEvaluationContext evalContext = integrationEvaluationContextFactoryBean.getObject();
when(context.getBean(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME, StandardEvaluationContext.class))

View File

@@ -18,6 +18,7 @@ package org.springframework.integration.config;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -28,8 +29,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
@@ -42,62 +42,56 @@ import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.util.Assert;
/**
* {@link FactoryBean} to populate {@link StandardEvaluationContext} instances enhanced with:
* <ul>
* <li>
* a {@link BeanFactoryResolver}.
* </li>
* <li>
* a {@link TypeConverter} based on the {@link ConversionService} from the application context.
* </li>
* <li>
* a set of provided {@link PropertyAccessor}s including a default {@link MapAccessor}.
* </li>
* <li>
* a set of provided SpEL functions.
* </li>
* </ul>
* <p/>
* This factory returns a new instance for each reference singleton - {@link #isSingleton()}
* returns false.
*
* @author Artem Bilan
* @author Gary Russell
* @since 3.0
*/
public class IntegrationEvaluationContextFactoryBean implements FactoryBean<StandardEvaluationContext>,
ApplicationContextAware, BeanFactoryAware, InitializingBean {
BeanFactoryAware, InitializingBean {
private volatile List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
private TypeConverter typeConverter = new StandardTypeConverter();
private volatile Map<String, Method> functions = new LinkedHashMap<String, Method>();
private ApplicationContext applicationContext;
private TypeConverter typeConverter = new StandardTypeConverter();
private BeanFactory beanFactory;
private BeanResolver beanResolver;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
this.beanResolver = new BeanFactoryResolver(this.applicationContext != null ? this.applicationContext : this.beanFactory);
this.loadDefaultPropertyAccessors(this.propertyAccessors);
if (this.applicationContext != null) {
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.applicationContext);
if (conversionService != null) {
this.typeConverter = new StandardTypeConverter(conversionService);
}
}
}
public void setPropertyAccessors(PropertyAccessor... accessors) {
Assert.noNullElements(accessors, "Cannot have null elements in accessors");
List<PropertyAccessor> propertyAccessors = new ArrayList<PropertyAccessor>();
loadDefaultPropertyAccessors(propertyAccessors);
for (PropertyAccessor accessor : accessors) {
propertyAccessors.add(accessor);
}
Collections.addAll(propertyAccessors, accessors);
this.propertyAccessors = propertyAccessors;
}
private void loadDefaultPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
propertyAccessors.add(new MapAccessor());
}
public void setFunctions(Map<String, Method> functionsArg) {
Map<String, Method> functions = new LinkedHashMap<String, Method>();
for (Entry<String, Method> function : functionsArg.entrySet()) {
@@ -107,20 +101,60 @@ public class IntegrationEvaluationContextFactoryBean implements FactoryBean<Stan
this.functions = functions;
}
public void addFunction(String name, Method method) {
this.functions.put(name, method);
}
public void addFunctions(Map<String, Method> functions) {
this.functions.putAll(functions);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.propertyAccessors.isEmpty()) {
this.loadDefaultPropertyAccessors(this.propertyAccessors);
}
if (this.beanFactory != null) {
this.beanResolver = new BeanFactoryResolver(this.beanFactory);
ConversionService conversionService = IntegrationContextUtils.getConversionService(this.beanFactory);
if (conversionService != null) {
this.typeConverter = new StandardTypeConverter(conversionService);
}
try {
SpelFunctionRegistrar functionRegistrar = this.beanFactory.getBean(SpelFunctionRegistrar.class);
if (functionRegistrar != null) {
this.addFunctions(functionRegistrar.getFunctions());
}
}
catch (NoSuchBeanDefinitionException e) {
//Ignore it.
//There is no <spel-function> components within application context.
}
}
}
@Override
public StandardEvaluationContext getObject() throws Exception {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setBeanResolver(this.beanResolver);
evaluationContext.setTypeConverter(this.typeConverter);
for (PropertyAccessor propertyAccessor : this.propertyAccessors) {
evaluationContext.addPropertyAccessor(propertyAccessor);
}
evaluationContext.setBeanResolver(this.beanResolver);
evaluationContext.setTypeConverter(this.typeConverter);
for (Entry<String, Method> functionEntry : this.functions.entrySet()) {
evaluationContext.registerFunction(functionEntry.getKey(), functionEntry.getValue());
}
return evaluationContext;
}
private void loadDefaultPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
propertyAccessors.add(new MapAccessor());
}
@Override
public Class<?> getObjectType() {
return StandardEvaluationContext.class;

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2013 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.lang.reflect.Method;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* <p>
* Utility class that keeps track of a Map of SpEL functions in order to register
* them with the "integrationEvaluationContext" upon initialization.
* </p>
* <p>
* The {@link org.springframework.integration.config.xml.SpelFunctionParser}
* doesn't register a bean for each &lt;spel-function&gt; within application context.
* There is no automatic way to get 'functions' from a parent context
* and this class provide a hook to get 'functions' from the parent context.
* </p>
*
* @author Artem Bilan
*
* @since 3.0
*/
class SpelFunctionRegistrar implements ApplicationContextAware, InitializingBean {
private final Map<String, Method> functions;
private ApplicationContext applicationContext;
SpelFunctionRegistrar(Map<String, Method> functions) {
this.functions = functions;
}
Map<String, Method> getFunctions() {
return functions;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
ApplicationContext parent = this.applicationContext.getParent();
if (parent != null) {
try {
SpelFunctionRegistrar parentFunctionRegistrar = parent.getBean(SpelFunctionRegistrar.class);
Map<String, Method> parentFunctions = parentFunctionRegistrar.getFunctions();
for (String key : parentFunctions.keySet()) {
if(!this.functions.containsKey(key)) {
this.functions.put(key, parentFunctions.get(key));
}
}
}
catch (NoSuchBeanDefinitionException e) {
//Ignore it.
//There is no <spel-function> components within parent application context.
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -24,6 +24,7 @@ package org.springframework.integration.config.xml;
* @author Oleg Zhurakousky
* @author David Turanski
* @author Gary Russell
* @author Artem Bilan
*/
public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHandler {
@@ -74,6 +75,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan
registerBeanDefinitionParser("control-bus", new ControlBusParser());
registerBeanDefinitionParser("wire-tap", new GlobalWireTapParser());
registerBeanDefinitionParser("transaction-synchronization-factory", new TransactionSynchronizationFactoryParser());
registerBeanDefinitionParser("spel-function", new SpelFunctionParser());
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2013 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.xml;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ClassUtils;
/**
* Parser for the &lt;spel-function&gt; element.
* Doesn't register a bean within application context, collects 'functions'
* within {@link org.springframework.integration.config.SpelFunctionRegistrar} bean.
*
* @author Artem Bilan
* @since 3.0
*/
public class SpelFunctionParser implements BeanDefinitionParser {
private final Map<String, Method> functions = new LinkedHashMap<String, Method>();
private volatile boolean initialized;
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
this.initializeSpelFunctionRegistrarIfNecessary(parserContext);
String id = element.getAttribute("id");
String className = element.getAttribute("class");
String signature = element.getAttribute("method");
Class<?> clazz = null;
try {
clazz = ClassUtils.forName(className, parserContext.getReaderContext().getBeanClassLoader());
}
catch (ClassNotFoundException e) {
parserContext.getReaderContext().error(e.getMessage(), element);
}
Method method = BeanUtils.resolveSignature(signature, clazz);
if (method == null) {
parserContext.getReaderContext().error(String.format("No declared method '%s' in class '%s'",
signature, className), element);
return null;
}
if (!Modifier.isStatic(method.getModifiers())) {
parserContext.getReaderContext().error("SpEL-function method has to be 'static'", element);
}
this.functions.put(id, method);
return null;
}
private synchronized void initializeSpelFunctionRegistrarIfNecessary(ParserContext parserContext) {
if (!this.initialized) {
BeanDefinitionBuilder registrarBuilder = BeanDefinitionBuilder
.genericBeanDefinition(IntegrationNamespaceUtils.BASE_PACKAGE + ".config.SpelFunctionRegistrar")
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addConstructorArgValue(this.functions);
BeanDefinitionReaderUtils.registerWithGeneratedName(registrarBuilder.getBeanDefinition(),
parserContext.getRegistry());
this.initialized = true;
}
}
}

View File

@@ -3883,6 +3883,40 @@ The list of component name patterns you want to track (e.g., tracked-components
</xsd:complexType>
</xsd:element>
<xsd:element name="spel-function">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Specifies the name of the SpEL function.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="class" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The fully qualified class name where to find the static method for SpEL function.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
The method signature in the form 'methodName[([arg_list])]',
where 'arg_list' is an optional, comma-separated list of fully-qualified
type names.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="control-bus-type">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -17,4 +17,8 @@
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
<int:spel-function id="bar" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
<int:spel-function id="barChild" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
</beans>

View File

@@ -19,4 +19,8 @@
<bean class="org.springframework.integration.expression.ParentContextTests$Foo" />
<int:spel-function id="bar" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
<int:spel-function id="barParent" class="org.springframework.integration.expression.ParentContextTests$Bar" method="bar"/>
</beans>

View File

@@ -19,14 +19,18 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
@@ -36,6 +40,7 @@ import org.springframework.integration.test.util.TestUtils;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 3.0
*
*/
@@ -51,17 +56,34 @@ public class ParentContextTests {
* and parent contexts work.
*/
@Test
public void testSpelBeanReferencesInChildAndParent() {
ApplicationContext parent = new ClassPathXmlApplicationContext("ParentContext-context.xml", this.getClass());
public void testSpelBeanReferencesInChildAndParent() throws ClassNotFoundException {
//To check if 'org.springframework.integration.config.SpelFunctionRegistrar#afterPropertiesSet()'
//doesn't throw any Exception
AbstractApplicationContext superParent = new GenericApplicationContext();
superParent.refresh();
AbstractApplicationContext parent = new ClassPathXmlApplicationContext(new String[]{"ParentContext-context.xml"},
this.getClass(), superParent);
Class<?> spelFunctionRegistrarClass = Class.forName("org.springframework.integration.config.SpelFunctionRegistrar");
Object parentSpelFunctionRegistrar = parent.getBean(spelFunctionRegistrarClass);
assertEquals(2, TestUtils.getPropertyValue(parentSpelFunctionRegistrar, "functions", Map.class).size());
assertEquals(2, evalContexts.size());
ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(parent);
child.setConfigLocation("org/springframework/integration/expression/ChildContext-context.xml");
child.refresh();
Object childSpelFunctionRegistrar = child.getBean(spelFunctionRegistrarClass);
Map functions = TestUtils.getPropertyValue(childSpelFunctionRegistrar, "functions", Map.class);
assertEquals(3, functions.size());
assertTrue(functions.containsKey("barParent"));
assertEquals(3, evalContexts.size());
assertSame(evalContexts.get(0).getBeanResolver(), evalContexts.get(1).getBeanResolver());
assertNotSame(evalContexts.get(1).getBeanResolver(), evalContexts.get(2).getBeanResolver());
assertSame(parent, TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
assertSame(child, TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
assertSame(parent.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(0).getBeanResolver(), "beanFactory"));
assertSame(child.getBeanFactory(), TestUtils.getPropertyValue(evalContexts.get(2).getBeanResolver(), "beanFactory"));
// Test transformer expressions
child.getBean("input", MessageChannel.class).send(new GenericMessage<String>("baz"));
@@ -82,4 +104,12 @@ public class ParentContextTests {
}
}
public static class Bar {
public static Object bar(Object o) {
return o;
}
}
}

View File

@@ -15,12 +15,16 @@
<transformer input-channel="beanResolvingInput" expression="@testBean.foo + payload.toUpperCase()" output-channel="output"/>
<transformer input-channel="spelFunctionInput" expression="#trim(payload)" output-channel="output"/>
<beans:bean id="testBean" class="org.springframework.integration.transformer.SpelTransformerIntegrationTests$TestBean"/>
<chain id="transformerChain" input-channel="transformerChainInput">
<transformer expression="null"/>
</chain>
<spel-function id="trim" class="org.springframework.util.StringUtils" method="trimWhitespace"/>
<beans:bean id="integrationEvaluationContext" class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
<beans:property name="propertyAccessors">
<util:list>
@@ -36,8 +40,11 @@
</beans:bean>
<channel id="fooin" />
<transformer id="foo" input-channel="fooin" expression="payload.bar" />
<channel id="functionIn" />
<transformer id="bar" input-channel="functionIn" expression="#bar(#root)" />
</beans:beans>

View File

@@ -47,6 +47,7 @@ import org.springframework.util.Assert;
/**
* @author Mark Fisher
* @author Artem Bilan
* @author Gary Russell
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -70,6 +71,9 @@ public class SpelTransformerIntegrationTests {
@Autowired @Qualifier("bar.handler")
private AbstractReplyProducingMessageHandler barHandler;
@Autowired
private MessageChannel spelFunctionInput;
@Test
public void simple() {
@@ -119,6 +123,14 @@ public class SpelTransformerIntegrationTests {
assertEquals("bar", reply.getPayload());
}
@Test
public void testInt1639SpelFunction() {
Message<?> message = MessageBuilder.withPayload(" foo ").build();
this.spelFunctionInput.send(message);
Message<?> result = output.receive(0);
assertEquals("foo", result.getPayload());
}
static class TestBean {
public String getFoo() {

View File

@@ -30,6 +30,9 @@
properties (using getters and setters). This is how the <interfacename>Message</interfacename> headers
and payload properties are accessible.
</para>
</section>
<section id="spel-customization">
<title>SpEL Evaluation Context Customization</title>
<para>
Starting with Spring Integration 3.0, it is possible to add additional
<interfacename>PropertyAccessor</interfacename>s to the SpEL evaluation context.
@@ -58,8 +61,7 @@
</property>
<property name="functions">
<map>
<entry key="barcalc"
value="#{T(foo.MyFunctions).getMethod('calc', T(foo.MyBar))}"/>
<entry key="barcalc" value="#{T(foo.MyFunctions).getMethod('calc', T(foo.MyBar))}"/>
</map>
</property>
</bean>]]></programlisting>
@@ -85,4 +87,63 @@
<code>"#barcalc(payload.myBar)"</code>.
</para>
</section>
<section id="spel-functions">
<title>SpEL Functions</title>
<para>
Namespace support is provided for easy addition of SpEL custom functions.
You can specify <code>&lt;spel-function/&gt;</code> components to provide
<ulink url="http://static.springsource.org/spring-framework/docs/current/spring-framework-reference/html/expressions.html#expressions-ref-functions">
custom SpEL functions</ulink> to the <interfacename>EvaluationContext</interfacename> used throughout the framework.
Instead of configuring the factory bean above, simply add one or more of these components
and the framework will automatically add them to the default <emphasis>integrationEvaluationContext</emphasis>
factory bean.
</para>
<para>For example, assuming we have a useful static method to evaluate XPath:</para>
<programlisting language="xml"><![CDATA[<int:spel-function id="xpath"
class="com.foo.test.XPathUtils" method="evaluate(java.lang.String, java.lang.Object)"/>
<int:transformer input-channel="in" output-channel="out"
expression="#xpath('//foo/@bar', payload)" />
]]></programlisting>
<para>
With this sample:
<itemizedlist>
<listitem>
The default <classname>IntegrationEvaluationContextFactoryBean</classname> bean with id
<emphasis>integrationEvaluationContext</emphasis> is registered with the application
context.
</listitem>
<listitem>
The <code>&lt;spel-function/&gt;</code> is parsed and added to the <code>functions</code>
Map of <emphasis>integrationEvaluationContext</emphasis> as map entry with <code>id</code> as the key
and the static <classname>Method</classname> as the value.
</listitem>
<listitem>
The <emphasis>integrationEvaluationContext</emphasis> factory bean creates a new
<classname>StandardEvaluationContext</classname> instance,
and it is configured with the default
<interfacename>PropertyAccessor</interfacename>s, <interfacename>BeanResolver</interfacename>
and the custom function.
</listitem>
<listitem>
That <interfacename>EvaluationContext</interfacename> instance is injected into the
<classname>ExpressionEvaluatingTransformer</classname> bean.
</listitem>
</itemizedlist>
</para>
<para>
<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 <emphasis>integrationEvaluationContext</emphasis> factory bean
because each needs a different <interfacename>BeanResolver</interfacename>, but the function
declarations are inherited (and can be overridden if needed by declaring a SpEL function with
the same name. The functions themselves are processed by the framework - they do not appear
as beans in the application context.
</note>
<note>
At this time, <interfacename>PropertyAccessor</interfacename>s are not inherited and must be
declared as described above in each context.
</note>
</para>
</section>
</appendix>

View File

@@ -64,9 +64,9 @@
<title>JMX Support</title>
<para>
A new <code>&lt;int-jmx:tree-polling-channel-adapter/&gt;</code> is provided; this
adapter queries the JMX MBean tree and sends a message with a payload that is the
graph of objects that matches the query. By default the MBeans are mapped to
primitives and simple Objects like Map, List and arrays - permitting simple
adapter queries the JMX MBean tree and sends a message with a payload that is the
graph of objects that matches the query. By default the MBeans are mapped to
primitives and simple Objects like Map, List and arrays - permitting simple
transformation, for example, to JSON
<xref linkend="jmx"/>.
</para>
@@ -82,11 +82,19 @@
<section id="3.0-spel-customization">
<title>Spring Expression Language (SpEL) Configuration</title>
<para>
A new factory bean is provided to allow
A new <classname>IntegrationEvaluationContextFactoryBean</classname> is provided to allow
configuration of custom <interfacename>PropertyAccessor</interfacename>s and functions for
use in SpEL expressions throughout the framework. For more information see <xref linkend="spel" />.
</para>
</section>
<section id="3.0-spel-functions">
<title>SpEL Functions Support</title>
<para>
To customize the SpEL <interfacename>EvaluationContext</interfacename> with static
<classname>Method</classname> functions the new <code>&lt;spel-function/&gt;</code>
component is introduced. For more information see <xref linkend="spel-functions" />.
</para>
</section>
</section>
<section id="3.0-general">
@@ -303,7 +311,7 @@
<para>
All Outbound Gateways (e.g. <code>&lt;jdbc:outbound-gateway/&gt;</code> or <code>&lt;jms:outbound-gateway/&gt;</code>)
are designed for 'request-reply' scenarios. A response is expected from the external service and
will be published to the <code>reply-channel</code>, or the <code>replyChannel</code> message header.
will be published to the <code>reply-channel</code>, or the <code>replyChannel</code> message header.
However, there are some cases where the external system might not always return a
result, e.g. a <code>&lt;jdbc:outbound-gateway/&gt;</code>, when a SELECT ends with an empty <interfacename>ResultSet</interfacename>
or, say, a Web Service is One-Way. An option is therefore needed to configure whether or not a