Reputation: 11
I am converting a Spring managed orchestration service to use Spring Integration as a proof of concept.
I have the basic app running, but have encountered a major bottleneck.
The app uses an inbound gateway to take an XML payload from the standard MultivaluedMap:
<int-http:inbound-gateway id="DSOWebServicePOC" request-channel="httpRequestsChannel"
reply-channel="httpResponsesChannel" path="/orchestrate/do" supported-methods="POST"
payload-expression="#requestParams.getFirst("XML")">
</int-http:inbound-gateway>
This is then sent via a direct channel to an xpath-splitter designed to break the request (which containts many similar sub-requests) down into each actual request that can be processed.
The performance of the application was about 6 times slower than the original service, and it is due to the method org.springframework.xml.xpath.Jaxp13XPathExpressionFactory$Jaxp13XPathExpression.evaluate(Node, QName)
This is because compiled XPath expressions are not thread safe, and the method in Jaxp13XPathExpression has a synchronized block - combined with Spring using singletons... In the original service, I used a thread local containing the complied XPathExressions.
I have tried custom scopes to no avail, and this explains why
Custom Spring Scope not working for Message Channel
I have followed the example for dynamic ftp, but use the thread name as the map key. I have combined this with a child application context containing the incoming channel, the splitter and the xpath-expression, but in the code, the channel resolver gets the bean by name from the new context:
I cannot do this for the XPathExpression as Jaxp13XPathExpression is a private static class within an abstract class with default access modifier.
How can I get a compiled XPathExpression with a different scope - whether it is request/session/thread, without just creating a service activator with a thread local and just not using the build in XML handling of the framework?
Further info:
Router:
<int:router input-channel="httpRequestsChannel" ref="channelResolver" method="resolve" />
Resolve Method:
public MessageChannel resolve() {
String thread = Thread.currentThread().getName();
ChannelResolverIntegrationBeans beans = this.integrationBeans
.get(thread);
if (beans == null) {
beans = createNewThreadIntegrationBeans(thread);
}
return beans.getChannel();
}
ChannelResolverIntegrationBeans method - the XPathExpression was my last attempt to get it to work, but it returns
Bean named 'dsoBatchRequestXPathNs' must be of type [javax.xml.xpath.XPathExpression], but was actually of type [org.springframework.xml.xpath.Jaxp13XPathExpressionFactory$Jaxp13XPathExpression]
private synchronized ChannelResolverIntegrationBeans createNewThreadIntegrationBeans(
String thread) {
ChannelResolverIntegrationBeans beans = this.integrationBeans
.get(thread);
if (beans == null) {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] { "/xmlChildHandlerContext.xml" }, this.appContext);
MessageChannel channel = ctx.getBean("fromDynamicRouter",
MessageChannel.class);
EventDrivenConsumer splitter = ctx.getBean("requestSplitter",
EventDrivenConsumer.class);
XPathExpression expression = ctx.getBean("dsoBatchRequestXPathNs",
XPathExpression.class);
beans = new ChannelResolverIntegrationBeans(channel, splitter);
this.integrationBeans.put(thread, beans);
// Will works as the same reference is presented always
this.contexts.put(beans, ctx);
}
return beans;
}
Child context beans:
<int:channel id="fromDynamicRouter" />
<int-xml:xpath-splitter id="requestSplitter"
input-channel="fromDynamicRouter" output-channel="xPathSplitterResultsChannel"
xpath-expression-ref="dsoBatchRequestXPathNs">
</int-xml:xpath-splitter>
<int-xml:xpath-expression id="dsoBatchRequestXPathNs"
expression="/dso:DsoRequests/dso:DsoRequest/*" namespace-map="namespaceMap" />
UDPATE
I have worked out that all the beans in the child context are actually different per channel by checking their hashcodes. All that was required was to create the child context and route to the incoming channel dynamically.
This hasn't really solved the performance problems though - it's still about twice as slow as the original service. Spring Integration just doesn't seem performant in this area unless I am missing something?
Upvotes: 1
Views: 424
Reputation: 163539
Not only is the JAXP XPathExpression non-thread-safe, but I guess you're probably using it to access a DOM, which isn't thread-safe either. Consider switching to Saxon, where both the compiled XPath expression and the native tree model are thread-safe, and as a bonus you get access to XPath 2.0.
Upvotes: 1