Files
spring-integration/src/reference/docbook/spel.xml
Gary Russell 04367659b4 INT-3342 Allow Custom TypeLocator for SpEL
JIRA: https://jira.spring.io/browse/INT-3342

Allow customization of the `TypeLocator` used by SpEL
`EvaluationContext`s throughout the framework.
2014-03-27 10:39:32 +02:00

240 lines
12 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="spel"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Spring Expression Language (SpEL)</title>
<section id="spel-intro">
<title>Introduction</title>
<para>
Many Spring Integration components can be configured using expressions. These expressions
are written in the <ulink url="http://static.springsource.org/spring-framework/docs/current/spring-framework-reference/html/expressions.html">
Spring Expression Language</ulink>.
</para>
<para>
In most cases, the <emphasis>#root</emphasis> object is the
<classname>Message</classname> which, of course, has two properties - <code>headers</code> and
<code>payload</code> - allowing such expressions as <code>payload</code>, <code>payload.foo</code>,
<code>headers['my.header']</code> etc.
</para>
<para>
In some cases, additional variables are provided, for example the <code>&lt;int-http:inbound-gateway/&gt;</code>
provides <code>#requestParams</code> (parameters from the HTTP request) and <code>#pathVariables</code>
(values from path placeholders in the URI).
</para>
<para>
For all SpEL expressions, a <interfacename>BeanResolver</interfacename> is available, enabling references to
any bean in the application context. For example <code>@myBean.foo(payload)</code>. In addition, two
<interfacename>PropertyAccessors</interfacename> are available; a <classname>MapAccessor</classname>
enables accessing values in a <interfacename>Map</interfacename> using a key, and a
<classname>ReflectivePropertyAccessor</classname> which allows access to fields and or JavaBean compliant
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 contexts used by
the framework. The framework provides
the <classname>JsonPropertyAccessor</classname> which can be used (read-only) to access fields from
a <classname>JsonNode</classname>, or JSON in a <classname>String</classname>. Or you can create your
own <interfacename>PropertyAccessor</interfacename> if you have specific needs.
</para>
<para>
In addition, custom functions can be added. Custom functions are <code>static</code> methods
declared on a class. Functions and property accessors are available in any SpEL
expression used throughout the framework.
</para>
<para>
The following configuration shows how to directly configure the
<classname>IntegrationEvaluationContextFactoryBean</classname> with custom property accessors
and functions. However, for convenience, namespace support is provided for both, as
described in the following sections, and the framework will automatically configure
the factory bean on your behalf.
</para>
<programlisting language="xml"><![CDATA[<bean id="integrationEvaluationContext"
class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
<property name="propertyAccessors">
<util:map>
<entry key="foo">
<bean class="foo.MyCustomPropertyAccessor"/>
</entry>
</util:map>
</property>
<property name="functions">
<map>
<entry key="barcalc" value="#{T(foo.MyFunctions).getMethod('calc', T(foo.MyBar))}"/>
</map>
</property>
</bean>]]></programlisting>
<para>
This factory bean definition will override the default <code>integrationEvaluationContext</code>
bean definition, adding the custom accessor to the list (which also includes the standard
accessors mentioned above), and one custom function.
</para>
<para>
Note that custom functions are static methods.
In the above example, the custom function is a static method <code>calc</code> on class
<classname>MyFunctions</classname> and takes a single parameter of type <classname>MyBar</classname>.
</para>
<para>
Say you have a <classname>Message</classname> with a payload that has a type <classname>MyFoo</classname>
on which you need to perform some action to create a <classname>MyBar</classname> object from it,
and you then want to invoke a custom function <code>calc</code> on that object.
</para>
<para>
The standard property accessors wouldn't know how to get a <classname>MyBar</classname>
from a <classname>MyFoo</classname> so you could write
and configure a custom property accessor to do so. So, your final expression might be
<code>"#barcalc(payload.myBar)"</code>.
</para>
<para>
The factory bean has another property <code>typeLocator</code> which allows you to customize
the <interfacename>TypeLocator</interfacename> used during SpEL evaluation. This might be
necessary when running in some environments that use a non-standard <classname>ClassLoader</classname>.
In the following example, SpEL expressions will always use the bean factory's class loader:
</para>
<programlisting language="xml"><![CDATA[<bean id="integrationEvaluationContext"
class="org.springframework.integration.config.IntegrationEvaluationContextFactoryBean">
<property name="typeLocator">
<bean class="org.springframework.expression.spel.support.StandardTypeLocator">
<constructor-arg value="#{beanFactory.beanClassLoader}"/>
</bean>
</property>
</bean>]]></programlisting>
</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 functions.
</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.
</note>
</para>
<para>
<emphasis role="bold">Built-in SpEL Functions</emphasis>
</para>
<para>
Spring Integration provides some standard functions, which are registered with the application
context automatically on start up:
<itemizedlist>
<listitem>
<emphasis role="bold">#jsonPath</emphasis> - to evaluate a 'jsonPath' on some provided object. This function
invokes <code>JsonPathUtils.evaluate(...)</code>. This static method delegates to the
<ulink url="http://code.google.com/p/json-path">Jayway JsonPath library</ulink>. The following shows
some usage examples:
<programlisting language="xml"><![CDATA[<transformer expression="#jsonPath(payload, '$.store.book[0].author')"/>
<filter expression="#jsonPath(payload,'$..book[2].isbn') matches '\d-\d{3}-\d{5}-\d'"/>
<splitter expression="#jsonPath(payload, '$.store.book')"/>
<router expression="#jsonPath(payload, headers.jsonPath)">
<mapping channel="output1" value="reference"/>
<mapping channel="output2" value="fiction"/>
</router>]]></programlisting>
#jsonPath also supports the third optional parameter - an array of
<ulink url="https://github.com/jayway/JsonPath/blob/master/json-path/src/main/java/com/jayway/jsonpath/Filter.java"
><classname>com.jayway.jsonpath.Filter</classname></ulink>, which could be provided by a reference to a
bean or bean method, for example.
<note>
Using this function requires the Jayway JsonPath library (json-path.jar) to be on
the classpath; otherwise the <emphasis>#jsonPath</emphasis>
SpEL function won't be registered.
</note>
For more information regarding JSON see 'JSON Transformers' in <xref linkend="transformer"/>.
</listitem>
<listitem>
<emphasis role="bold">#xpath</emphasis> - to evaluate an 'xpath' on some provided object.
For more information regarding xml and xpath see <xref linkend="xml"/>.
</listitem>
<!--<listitem>
<emphasis>#auth</emphasis> - TBD
</listitem>-->
</itemizedlist>
</para>
</section>
<section id="spel-property-accessors">
<title>PropertyAccessors</title>
<para>
Namespace support is provided for the easy addition of SpEL custom
<ulink url="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/expression/PropertyAccessor.html"
><interfacename>PropertyAccessor</interfacename></ulink>
implementations. You can specify the <code>&lt;spel-property-accessors/&gt;</code> component to provide a list of
custom <interfacename>PropertyAccessor</interfacename>s 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 the accessors to the default
<emphasis>integrationEvaluationContext</emphasis> factory bean:
</para>
<programlisting language="xml"><![CDATA[<int:spel-property-accessors>
<bean id="jsonPA" class="org.springframework.integration.json.JsonPropertyAccessor"/>
<ref bean="fooPropertyAccessor"/>
</int:spel-property-accessors>
]]></programlisting>
<para>
With this sample, two custom <interfacename>PropertyAccessor</interfacename>s will be injected to the
<interfacename>EvaluationContext</interfacename> in the order that they are declared.
</para>
<para>
<note>
Custom <interfacename>PropertyAccessor</interfacename>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
<classname>org.springframework.context.expression.MapAccessor</classname> and
<classname>org.springframework.expression.spel.support.ReflectivePropertyAccessor</classname>).
If a <interfacename>PropertyAccessor</interfacename> with the same bean id is declared in a child context(s),
it will override the parent accessor. Beans declared within a <code>&lt;spel-property-accessors/&gt;</code>
must have an 'id' attribute.
The final order of usage is: the accessors in the current context,
in the order in which they are declared, followed by any from parent contexts, in order, followed
by the <classname>MapAccessor</classname> and finally the <classname>ReflectivePropertyAccessor</classname>.
</note>
</para>
</section>
</appendix>