INT-2994 Advice Chain Config via Annotations

Allow configuration of request handler advice chain using

- ServiceActivator
- Filter
- Splitter
- Transformer

annotations.

Also, with splitter, allow setting discardWithinAdvice (See
INT-2938).

INT-2994 Polishing: PR Comments

- Change adviceChain attribute to an array
- Use a boolean for the discardWithinAdvice attribute

INT-2994 Handler Advice Doc Polishing

Add a paragraph about Advice Order.
This commit is contained in:
Gary Russell
2013-04-16 11:29:20 -04:00
committed by Gary Russell
parent 7ea5998016
commit 573c692957
17 changed files with 347 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -33,8 +33,9 @@ import java.lang.annotation.Target;
* as Message parameters by using the {@link Header @Header} parameter annotation.
* <p>
* The return type of the annotated method must be a boolean (or Boolean).
*
*
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
@Target(ElementType.METHOD)
@@ -46,4 +47,8 @@ public @interface Filter {
String outputChannel() default "";
String[] adviceChain() default {};
boolean discardWithinAdvice() default true;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -36,8 +36,9 @@ import java.lang.annotation.Target;
* Return values from the annotated method may be of any type. If the return
* value is not a Message, a reply Message will be created with that object
* as its payload.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -49,4 +50,6 @@ public @interface ServiceActivator {
String outputChannel() default "";
String[] adviceChain() default {};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -36,8 +36,9 @@ import java.lang.annotation.Target;
* Return values from the annotated method may be either a Collection or Array
* with elements of any type. If the type is not a Message, each will be used
* as the payload for creating a new Message.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@@ -48,4 +49,6 @@ public @interface Splitter {
String outputChannel() default "";
String[] adviceChain() default {};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -26,8 +26,9 @@ import java.lang.annotation.Target;
/**
* Indicates that a method is capable of transforming a message, message header,
* or message payload.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@@ -39,4 +40,6 @@ public @interface Transformer {
String outputChannel() default "";
String[] adviceChain() default {};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -18,6 +18,11 @@ package org.springframework.integration.config.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -31,6 +36,7 @@ import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.integration.support.channel.ChannelResolver;
import org.springframework.util.Assert;
@@ -40,11 +46,14 @@ import org.springframework.util.StringUtils;
* Base class for Method-level annotation post-processors.
*
* @author Mark Fisher
* @author Gary Russell
*/
public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation> implements MethodAnnotationPostProcessor<T> {
private static final String INPUT_CHANNEL_ATTRIBUTE = "inputChannel";
private static final String ADVICE_CHAIN_ATTRIBUTE = "adviceChain";
protected final BeanFactory beanFactory;
@@ -60,6 +69,7 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
public Object postProcess(Object bean, String beanName, Method method, T annotation) {
MessageHandler handler = this.createHandler(bean, method, annotation);
setAdviceChainIfPresent(beanName, annotation, handler);
if (handler instanceof Orderable) {
Order orderAnnotation = AnnotationUtils.findAnnotation(method, Order.class);
if (orderAnnotation != null) {
@@ -76,6 +86,40 @@ public abstract class AbstractMethodAnnotationPostProcessor<T extends Annotation
return handler;
}
protected final void setAdviceChainIfPresent(String beanName, T annotation, MessageHandler handler) {
String[] adviceChainNames = (String[]) AnnotationUtils.getValue(annotation, ADVICE_CHAIN_ATTRIBUTE);
if (adviceChainNames != null && adviceChainNames.length > 0) {
if (!(handler instanceof AbstractReplyProducingMessageHandler)) {
throw new IllegalArgumentException("Cannot apply advice chain to " + handler.getClass().getName());
}
List<Advice> adviceChain = new ArrayList<Advice>();
for (String adviceChainName : adviceChainNames) {
Object adviceChainBean = this.beanFactory.getBean(adviceChainName);
if (adviceChainBean instanceof Advice) {
adviceChain.add((Advice) adviceChainBean);
}
else if (adviceChainBean instanceof Advice[]) {
for (Advice advice : (Advice[]) adviceChainBean) {
adviceChain.add(advice);
}
}
else if (adviceChainBean instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Advice> adviceChainEntries = (Collection<Advice>) adviceChainBean;
for (Advice advice : adviceChainEntries) {
adviceChain.add(advice);
}
}
else {
throw new IllegalArgumentException("Invalid advice chain type:" +
adviceChainName.getClass().getName() + " for bean '" + beanName + "'");
}
}
((AbstractReplyProducingMessageHandler) handler).setAdviceChain(adviceChain);
}
}
protected boolean shouldCreateEndpoint(T annotation) {
return (StringUtils.hasText((String) AnnotationUtils.getValue(annotation, INPUT_CHANNEL_ATTRIBUTE)));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -30,6 +30,7 @@ import org.springframework.util.StringUtils;
* Post-processor for Methods annotated with {@link Filter @Filter}.
*
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostProcessor<Filter> {
@@ -49,6 +50,7 @@ public class FilterAnnotationPostProcessor extends AbstractMethodAnnotationPostP
if (StringUtils.hasText(outputChannelName)) {
filter.setOutputChannel(this.channelResolver.resolveChannelName(outputChannelName));
}
filter.setDiscardWithinAdvice(annotation.discardWithinAdvice());
return filter;
}

View File

@@ -0,0 +1,39 @@
/*
* 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.annotation;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.endpoint.annotation.TestService;
/**
* @author Gary Russell
*/
@MessageEndpoint
public class AnnotatedTestServiceWithAdvice implements TestService {
@ServiceActivator(inputChannel="inputChannel", outputChannel="outputChannel")
public String sayHello(String name) {
return "hello " + name;
}
@ServiceActivator(inputChannel="advisedIn", outputChannel="advisedOut", adviceChain="advice")
public String sayHelloWithAdvice(String name) {
return "hello " + name;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -16,8 +16,15 @@
package org.springframework.integration.config.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -27,12 +34,15 @@ import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.test.util.TestUtils.TestApplicationContext;
/**
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class FilterAnnotationPostProcessorTests {
@@ -45,7 +55,6 @@ public class FilterAnnotationPostProcessorTests {
private final QueueChannel outputChannel = new QueueChannel();
@Before
public void init() {
context.registerChannel("input", inputChannel);
@@ -60,6 +69,99 @@ public class FilterAnnotationPostProcessorTests {
testValidFilter(new TestFilterWithBooleanPrimitive());
}
@Test
public void filterAnnotationWithAdviceDiscardWithin() {
TestAdvice advice = new TestAdvice();
context.registerBean("adviceChain", advice);
testValidFilter(new TestFilterWithAdviceDiscardWithin());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceDiscardWithinTwice() {
TestAdvice advice1 = new TestAdvice();
TestAdvice advice2 = new TestAdvice();
context.registerBean("adviceChain1", advice1);
context.registerBean("adviceChain2", advice2);
testValidFilter(new TestFilterWithAdviceDiscardWithinTwice());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
List<?> adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class);
assertEquals(2, adviceList.size());
assertSame(advice1, adviceList.get(0));
assertSame(advice2, adviceList.get(1));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceDiscardWithout() {
TestAdvice advice = new TestAdvice();
context.registerBean("adviceChain", advice);
testValidFilter(new TestFilterWithAdviceDiscardWithout());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0));
assertFalse(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceArray() {
TestAdvice advice = new TestAdvice();
context.registerBean("adviceChain", new TestAdvice[] {advice});
testValidFilter(new TestFilterWithAdviceDiscardWithin());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceArrayTwice() {
TestAdvice advice1 = new TestAdvice();
TestAdvice advice2 = new TestAdvice();
context.registerBean("adviceChain1", new TestAdvice[] {advice1, advice2});
TestAdvice advice3 = new TestAdvice();
TestAdvice advice4 = new TestAdvice();
context.registerBean("adviceChain2", new TestAdvice[] {advice3, advice4});
testValidFilter(new TestFilterWithAdviceDiscardWithinTwice());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
List<?> adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class);
assertEquals(4, adviceList.size());
assertSame(advice1, adviceList.get(0));
assertSame(advice2, adviceList.get(1));
assertSame(advice3, adviceList.get(2));
assertSame(advice4, adviceList.get(3));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceCollection() {
TestAdvice advice = new TestAdvice();
context.registerBean("adviceChain", Arrays.asList(new TestAdvice[] {advice}));
testValidFilter(new TestFilterWithAdviceDiscardWithin());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
assertSame(advice, TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class).get(0));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithAdviceCollectionTwice() {
TestAdvice advice1 = new TestAdvice();
TestAdvice advice2 = new TestAdvice();
context.registerBean("adviceChain1", new TestAdvice[] {advice1, advice2});
TestAdvice advice3 = new TestAdvice();
TestAdvice advice4 = new TestAdvice();
context.registerBean("adviceChain2", new TestAdvice[] {advice3, advice4});
testValidFilter(new TestFilterWithAdviceDiscardWithinTwice());
EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("testFilter.filter.filter");
List<?> adviceList = TestUtils.getPropertyValue(endpoint, "handler.adviceChain", List.class);
assertEquals(4, adviceList.size());
assertSame(advice1, adviceList.get(0));
assertSame(advice2, adviceList.get(1));
assertSame(advice3, adviceList.get(2));
assertSame(advice4, adviceList.get(3));
assertTrue(TestUtils.getPropertyValue(endpoint, "handler.postProcessWithinAdvice", Boolean.class));
}
@Test
public void filterAnnotationWithBooleanWrapperClass() {
testValidFilter(new TestFilterWithBooleanWrapperClass());
@@ -91,7 +193,6 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
@SuppressWarnings("unused")
private static class TestFilterWithBooleanPrimitive {
@Filter(inputChannel="input", outputChannel="output")
@@ -100,9 +201,35 @@ public class FilterAnnotationPostProcessorTests {
}
}
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithin {
@Filter(inputChannel="input", outputChannel="output", adviceChain="adviceChain")
public boolean filter(String s) {
return !s.contains("bad");
}
}
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithinTwice {
@Filter(inputChannel="input", outputChannel="output", adviceChain={"adviceChain1", "adviceChain2"})
public boolean filter(String s) {
return !s.contains("bad");
}
}
@MessageEndpoint
private static class TestFilterWithAdviceDiscardWithout {
@Filter(inputChannel="input", outputChannel="output",
adviceChain="adviceChain", discardWithinAdvice=false)
public boolean filter(String s) {
return !s.contains("bad");
}
}
@MessageEndpoint
@SuppressWarnings("unused")
private static class TestFilterWithBooleanWrapperClass {
@Filter(inputChannel="input", outputChannel="output")
@@ -113,7 +240,6 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
@SuppressWarnings("unused")
private static class TestFilterWithStringReturnType {
@Filter(inputChannel="input", outputChannel="output")
@@ -124,7 +250,6 @@ public class FilterAnnotationPostProcessorTests {
@MessageEndpoint
@SuppressWarnings("unused")
private static class TestFilterWithVoidReturnType {
@Filter(inputChannel="input", outputChannel="output")
@@ -132,4 +257,12 @@ public class FilterAnnotationPostProcessorTests {
}
}
public static class TestAdvice extends AbstractRequestHandlerAdvice {
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
return callback.execute();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 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.
@@ -37,6 +37,7 @@ import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
@@ -46,6 +47,7 @@ import org.springframework.integration.test.util.TestUtils.TestApplicationContex
/**
* @author Mark Fisher
* @author Gary Russell
*/
public class MessagingAnnotationPostProcessorTests {
@@ -82,9 +84,16 @@ public class MessagingAnnotationPostProcessorTests {
context.start();
MessageChannel inputChannel = (MessageChannel) context.getBean("inputChannel");
PollableChannel outputChannel = (PollableChannel) context.getBean("outputChannel");
inputChannel.send(new GenericMessage<String>("world"));
GenericMessage<String> messageToSend = new GenericMessage<String>("world");
inputChannel.send(messageToSend);
Message<?> message = outputChannel.receive(1000);
assertEquals("hello world", message.getPayload());
inputChannel = context.getBean("advisedIn", MessageChannel.class);
outputChannel = context.getBean("advisedOut", PollableChannel.class);
inputChannel.send(messageToSend);
message = outputChannel.receive(1000);
assertEquals("hello world advised", message.getPayload());
context.stop();
}
@@ -301,7 +310,7 @@ public class MessagingAnnotationPostProcessorTests {
private String messageText;
private CountDownLatch latch;
private final CountDownLatch latch;
public OutboundOnlyTestBean(CountDownLatch latch) {
@@ -313,7 +322,6 @@ public class MessagingAnnotationPostProcessorTests {
}
@ServiceActivator(inputChannel="testChannel")
@SuppressWarnings("unused")
public void countdown(String input) {
this.messageText = input;
latch.countDown();
@@ -343,7 +351,6 @@ public class MessagingAnnotationPostProcessorTests {
@MessageEndpoint
private static class ServiceActivatorAnnotatedBean {
@SuppressWarnings("unused")
@ServiceActivator(inputChannel="inputChannel")
public String test(String s) {
return s + s;
@@ -355,11 +362,18 @@ public class MessagingAnnotationPostProcessorTests {
@MessageEndpoint
private static class TransformerAnnotationTestBean {
@SuppressWarnings("unused")
@Transformer(inputChannel="inputChannel", outputChannel="outputChannel")
public String transformBefore(String input) {
return input.toUpperCase();
}
}
public static class ServiceActivatorAdvice extends AbstractRequestHandlerAdvice {
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
return callback.execute() + " advised";
}
}
}

View File

@@ -15,6 +15,15 @@
<integration:queue capacity="5"/>
</integration:channel>
<bean id="endpoint" class="org.springframework.integration.config.annotation.AnnotatedTestService"/>
<bean id="endpoint" class="org.springframework.integration.config.annotation.AnnotatedTestServiceWithAdvice"/>
<integration:channel id="advisedIn"/>
<integration:channel id="advisedOut">
<integration:queue capacity="1"/>
</integration:channel>
<bean id="advice"
class="org.springframework.integration.config.annotation.MessagingAnnotationPostProcessorTests$ServiceActivatorAdvice"/>
</beans>

View File

@@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
*/
public abstract class TestUtils {
@@ -142,11 +143,15 @@ public abstract class TestUtils {
"channel name has already been set with a conflicting value");
}
}
registerBean(channelName, channel, this);
TestUtils.registerBean(channelName, channel, this);
}
public void registerEndpoint(String endpointName, AbstractEndpoint endpoint) {
registerBean(endpointName, endpoint, this);
TestUtils.registerBean(endpointName, endpoint, this);
}
public void registerBean(String beanName, Object bean) {
TestUtils.registerBean(beanName, bean, this);
}
}

View File

@@ -326,6 +326,9 @@ public class FooService {
still available. For more detail see <xref linkend="bridge"/>.
</note>
</para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>
</section>
<section id="message-mapping-rules">

View File

@@ -164,6 +164,10 @@
<para>The filter can be either referenced explicitly from XML or, if
the <interfacename>@MessageEndpoint</interfacename> annotation is defined
on the class, detected automatically through classpath scanning.</para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>
</section>
</section>
</section>

View File

@@ -458,6 +458,13 @@ protected abstract Object doInvoke(ExecutionCallback callback, Object target, Me
</para>
</note>
</section>
<section id="other-advice">
<title>Other Advice Chain Elements</title>
<para>
While the abstract class mentioned above is provided as a convenience, you can add any <classname>
Advice</classname> to the chain, including a transaction advice.
</para>
</section>
<section id="advising-filters">
<title>Advising Filters</title>
<para>
@@ -474,4 +481,39 @@ protected abstract Object doInvoke(ExecutionCallback callback, Object target, Me
(or exception) occurs after the advice chain is called.
</para>
</section>
<section id="advising-with-annotations">
<title>Advising Endpoints Using Annotations</title>
<para>
When configuring certain endpoints using annotations (<code>@Filter</code>, <code>@ServiceActivator</code>,
<code>@Splitter</code>, and <code>@Transformer</code>), you can supply a bean name for the advice
chain in the <code>adviceChain</code> attribute. In addition, the <code>@Filter</code> annotation
also has the <code>discardWithinAdvice</code> attribute, which can be used to configure the discard
behavior as discussed in <xref linkend="advising-filters"/>. An example with the discard being
performed after the advice is shown below.
</para>
<programlisting language="java"><![CDATA[@MessageEndpoint
public class MyAdvisedFilter {
@Filter(inputChannel="input", outputChannel="output",
adviceChain="adviceChain", discardWithinAdvice="false")
public boolean filter(String s) {
return s.contains("good");
}
}]]></programlisting>
</section>
<section id="Advice Order">
<title>Ordering Advices within an Advice Chain</title>
<para>
Advice classes are "around" advices and are applied in a nested fashion. The first advice is the
outermost, the last advice the innermost (closest to the handler being advised). It is important
to put the advice classes in the correct order to achieve the functionality you desire.
</para>
<para>
For example, let's say you want to add a retry advice and a transaction advice.
You may want to place the retry advice advice first, followed by the transaction advice.
Then, each retry will be performed in a new transaction. On the other hand, if you want all the attempts,
and any recovery operations (in the retry <classname>RecoveryCallback</classname>), to be scoped within
the transaction, you would put the transaction advice first.
</para>
</section>
</section>

View File

@@ -161,6 +161,9 @@
List&lt;LineItem&gt; extractItems(Order order) {
return order.getItems()
}</programlisting></para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>
</section>
</section>
</section>

View File

@@ -350,6 +350,9 @@ Order generateOrder(String productId, @Header("customerName") String customer) {
return new Order(productId, customer);
}</programlisting>
</para>
<para>
Also see <xref linkend="advising-with-annotations"/>.
</para>
</section>
</section>

View File

@@ -92,6 +92,13 @@
be performed after the advice chain completes. See <xref linkend="advising-filters"/>.
</para>
</section>
<section id="3.0-annotation-advice">
<title>Advising Endpoints using Annotations</title>
<para>
Request Handler Advice Chains can now be configured using annotations. See
<xref linkend="advising-with-annotations"/>.
</para>
</section>
<section id="3.0-o-t-s-t">
<title>ObjectToStringTransformer Improvements</title>
<para>