Add support for CacheRequestHandlerAdvice
* Fix `AbstractMessageProcessingTransformer` to react for the `AbstractIntegrationMessageBuilder` invocation result and don't wrap it into the `Message` * Demonstrate functionality in the `CacheRequestHandlerAdviceTests` * Polishing and Docs * Fix JavaDocs warnings Doc polishing.
This commit is contained in:
committed by
Gary Russell
parent
36581bfed8
commit
735e82e721
@@ -688,7 +688,7 @@ public class HeaderEnricherSpec extends ConsumerEndpointSpec<HeaderEnricherSpec,
|
||||
/**
|
||||
* Add a {@link IntegrationMessageHeaderAccessor#ROUTING_SLIP} header.
|
||||
* The possible values are:
|
||||
* <p><ul>
|
||||
* <ul>
|
||||
* <li>A {@link org.springframework.messaging.MessageChannel} instance.
|
||||
* <li>A {@link org.springframework.messaging.MessageChannel} bean name.
|
||||
* <li>A {@link org.springframework.integration.routingslip.RoutingSlipRouteStrategy} instance.
|
||||
@@ -696,7 +696,7 @@ public class HeaderEnricherSpec extends ConsumerEndpointSpec<HeaderEnricherSpec,
|
||||
* <li>A {@code String} for SpEL expression which has to be evaluated to the
|
||||
* {@link org.springframework.messaging.MessageChannel} or
|
||||
* {@link org.springframework.integration.routingslip.RoutingSlipRouteStrategy}.
|
||||
* </ul><p>
|
||||
* </ul>
|
||||
* If the header exists, it will <b>not</b> be overwritten unless {@link #defaultOverwrite(boolean)} is true.
|
||||
* @param routingSlipPath the header value for {@link IntegrationMessageHeaderAccessor#ROUTING_SLIP}.
|
||||
* @return the header enricher spec.
|
||||
@@ -709,7 +709,7 @@ public class HeaderEnricherSpec extends ConsumerEndpointSpec<HeaderEnricherSpec,
|
||||
/**
|
||||
* Add a {@link IntegrationMessageHeaderAccessor#ROUTING_SLIP} header.
|
||||
* The possible values are:
|
||||
* <p><ul>
|
||||
* <ul>
|
||||
* <li>A {@link org.springframework.messaging.MessageChannel} instance.
|
||||
* <li>A {@link org.springframework.messaging.MessageChannel} bean name.
|
||||
* <li>A {@link org.springframework.integration.routingslip.RoutingSlipRouteStrategy} instance.
|
||||
@@ -717,7 +717,7 @@ public class HeaderEnricherSpec extends ConsumerEndpointSpec<HeaderEnricherSpec,
|
||||
* <li>A {@code String} for SpEL expression which has to be evaluated to the
|
||||
* {@link org.springframework.messaging.MessageChannel} or
|
||||
* {@link org.springframework.integration.routingslip.RoutingSlipRouteStrategy}.
|
||||
* </ul><p>
|
||||
* </ul>
|
||||
* @param overwrite true to overwrite an existing header.
|
||||
* @param routingSlipPath the header value for {@link IntegrationMessageHeaderAccessor#ROUTING_SLIP}.
|
||||
* @return the header enricher spec.
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.handler.advice;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.interceptor.CacheAspectSupport;
|
||||
import org.springframework.cache.interceptor.CacheErrorHandler;
|
||||
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||
import org.springframework.cache.interceptor.CacheOperation;
|
||||
import org.springframework.cache.interceptor.CacheOperationInvoker;
|
||||
import org.springframework.cache.interceptor.CachePutOperation;
|
||||
import org.springframework.cache.interceptor.CacheResolver;
|
||||
import org.springframework.cache.interceptor.CacheableOperation;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.expression.FunctionExpression;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* The {@link AbstractRequestHandlerAdvice} implementation for caching
|
||||
* {@link AbstractReplyProducingMessageHandler.RequestHandler#handleRequestMessage(Message)} results.
|
||||
* Supports all the cache operations - cacheable, put, evict.
|
||||
* By default only cacheable is applied for the provided {@code cacheNames}.
|
||||
* The default cache {@code key} is {@code payload} of the request message.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.2
|
||||
*
|
||||
* @see AbstractReplyProducingMessageHandler.RequestHandler
|
||||
* @see CacheAspectSupport
|
||||
* @see CacheOperation
|
||||
*/
|
||||
public class CacheRequestHandlerAdvice extends AbstractRequestHandlerAdvice
|
||||
implements SmartInitializingSingleton {
|
||||
|
||||
private static final Method HANDLE_REQUEST_METHOD =
|
||||
ReflectionUtils.findMethod(AbstractReplyProducingMessageHandler.RequestHandler.class,
|
||||
"handleRequestMessage", Message.class);
|
||||
|
||||
private final IntegrationCacheAspect delegate = new IntegrationCacheAspect();
|
||||
|
||||
private final String[] cacheNames;
|
||||
|
||||
private final List<CacheOperation> cacheOperations = new ArrayList<>();
|
||||
|
||||
private Expression keyExpression = new FunctionExpression<Message<?>>(Message::getPayload);
|
||||
|
||||
/**
|
||||
* Create a {@link CacheRequestHandlerAdvice} instance based on the provided name of caches
|
||||
* and {@link CacheableOperation} as default one.
|
||||
* This can be overridden by the {@link #setCacheOperations}.
|
||||
* @param cacheNames the name of caches to use in the advice.
|
||||
* @see #setCacheOperations
|
||||
*/
|
||||
public CacheRequestHandlerAdvice(String... cacheNames) {
|
||||
this.cacheNames = cacheNames;
|
||||
CacheableOperation.Builder builder = new CacheableOperation.Builder();
|
||||
builder.setName(toString());
|
||||
this.cacheOperations.add(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a set of {@link CacheOperation} which are going to be applied to the
|
||||
* {@link AbstractReplyProducingMessageHandler.RequestHandler#handleRequestMessage(Message)}
|
||||
* method via {@link IntegrationCacheAspect}.
|
||||
* This is similar to the technique provided by the
|
||||
* {@link org.springframework.cache.annotation.Caching} annotation.
|
||||
* @param cacheOperations the array of {@link CacheOperation} to use.
|
||||
* @see org.springframework.cache.annotation.Caching
|
||||
*/
|
||||
public void setCacheOperations(CacheOperation... cacheOperations) {
|
||||
Assert.notEmpty(cacheOperations, "'cacheOperations' must not be empty");
|
||||
Assert.notNull(cacheOperations, "'cacheOperations' must not be null");
|
||||
this.cacheOperations.clear();
|
||||
this.cacheOperations.addAll(Arrays.asList(cacheOperations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a common {@link CacheManager} if some {@link CacheOperation} comes without it.
|
||||
* See {@link org.springframework.cache.annotation.CacheConfig} annotation for similar approach.
|
||||
* @param cacheManager the {@link CacheManager} to use.
|
||||
* @see org.springframework.cache.annotation.CacheConfig
|
||||
*/
|
||||
public void setCacheManager(CacheManager cacheManager) {
|
||||
this.delegate.setCacheManager(cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a common {@link CacheResolver} if some {@link CacheOperation} comes without it.
|
||||
* See {@link org.springframework.cache.annotation.CacheConfig} for similar approach.
|
||||
* @param cacheResolver the {@link CacheResolver} to use.
|
||||
* @see org.springframework.cache.annotation.CacheConfig
|
||||
*/
|
||||
public void setCacheResolver(CacheResolver cacheResolver) {
|
||||
this.delegate.setCacheResolver(cacheResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link CacheErrorHandler} instance to use to handle errors
|
||||
* thrown by the cache provider.
|
||||
* @param errorHandler the {@link CacheErrorHandler} to use.
|
||||
* @see CacheAspectSupport#setErrorHandler(CacheErrorHandler)
|
||||
*/
|
||||
public void setErrorHandler(CacheErrorHandler errorHandler) {
|
||||
Assert.notNull(errorHandler, "'errorHandler' must not be null");
|
||||
this.delegate.setErrorHandler(errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an expression in SpEL style to evaluate a cache key at runtime
|
||||
* against a request message.
|
||||
* @param keyExpression the expression to use for cache key generation.
|
||||
*/
|
||||
public void setKeyExpressionString(String keyExpression) {
|
||||
Assert.hasText(keyExpression, "'keyExpression' must not be empty");
|
||||
setKeyExpression(EXPRESSION_PARSER.parseExpression(keyExpression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link Function} to evaluate a cache key at runtime
|
||||
* against a request message.
|
||||
* @param keyFunction the {@link Function} to use for cache key generation.
|
||||
*/
|
||||
public void setKeyFunction(Function<Message<?>, ?> keyFunction) {
|
||||
Assert.notNull(keyFunction, "'keyFunction' must not be null");
|
||||
setKeyExpression(new FunctionExpression<>(keyFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a SpEL expression to evaluate a cache key at runtime
|
||||
* against a request message.
|
||||
* @param keyExpression the expression to use for cache key generation.
|
||||
*/
|
||||
public void setKeyExpression(Expression keyExpression) {
|
||||
Assert.notNull(keyExpression, "'keyExpression' must not be null");
|
||||
this.keyExpression = keyExpression;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.delegate.afterSingletonsInstantiated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
List<CacheOperation> cacheOperations;
|
||||
if (!ObjectUtils.isEmpty(this.cacheNames)) {
|
||||
cacheOperations = this.cacheOperations.stream()
|
||||
.filter((operation) -> ObjectUtils.isEmpty(operation.getCacheNames()))
|
||||
.map((operation) -> {
|
||||
CacheOperation.Builder builder;
|
||||
if (operation instanceof CacheableOperation) {
|
||||
CacheableOperation cacheableOperation = (CacheableOperation) operation;
|
||||
CacheableOperation.Builder cacheableBuilder = new CacheableOperation.Builder();
|
||||
cacheableBuilder.setSync(cacheableOperation.isSync());
|
||||
String unless = cacheableOperation.getUnless();
|
||||
if (unless != null) {
|
||||
cacheableBuilder.setUnless(unless);
|
||||
}
|
||||
builder = cacheableBuilder;
|
||||
}
|
||||
else if (operation instanceof CacheEvictOperation) {
|
||||
CacheEvictOperation.Builder cacheEvictBuilder = new CacheEvictOperation.Builder();
|
||||
CacheEvictOperation cacheEvictOperation = (CacheEvictOperation) operation;
|
||||
cacheEvictBuilder.setBeforeInvocation(cacheEvictOperation.isBeforeInvocation());
|
||||
cacheEvictBuilder.setCacheWide(cacheEvictOperation.isCacheWide());
|
||||
builder = cacheEvictBuilder;
|
||||
}
|
||||
else {
|
||||
CachePutOperation cachePutOperation = (CachePutOperation) operation;
|
||||
CachePutOperation.Builder cachePutBuilder = new CachePutOperation.Builder();
|
||||
String unless = cachePutOperation.getUnless();
|
||||
if (unless != null) {
|
||||
cachePutBuilder.setUnless(unless);
|
||||
}
|
||||
builder = cachePutBuilder;
|
||||
}
|
||||
|
||||
builder.setName(operation.getName());
|
||||
builder.setCacheManager(operation.getCacheManager());
|
||||
builder.setCacheNames(this.cacheNames);
|
||||
builder.setCacheResolver(operation.getCacheResolver());
|
||||
builder.setCondition(operation.getCondition());
|
||||
builder.setKey(operation.getKey());
|
||||
builder.setKeyGenerator(operation.getKeyGenerator());
|
||||
return builder.build();
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
cacheOperations = this.cacheOperations;
|
||||
}
|
||||
|
||||
this.delegate.setBeanFactory(getBeanFactory());
|
||||
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
this.delegate.setKeyGenerator((target, method, params) ->
|
||||
this.keyExpression.getValue(evaluationContext, params[0])); // NOSONAR
|
||||
this.delegate.setCacheOperationSources((method, targetClass) -> cacheOperations);
|
||||
this.delegate.afterPropertiesSet();
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
|
||||
CacheOperationInvoker operationInvoker =
|
||||
() -> {
|
||||
Object result = callback.execute();
|
||||
// Drop MessageBuilder optimization in favor of Serializable support in cache implementation.
|
||||
if (result instanceof AbstractIntegrationMessageBuilder<?>) {
|
||||
return ((AbstractIntegrationMessageBuilder<?>) result).build();
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return this.delegate.invoke(operationInvoker, target, message);
|
||||
}
|
||||
|
||||
private static class IntegrationCacheAspect extends CacheAspectSupport {
|
||||
|
||||
IntegrationCacheAspect() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Object invoke(CacheOperationInvoker invoker, Object target, Message<?> message) {
|
||||
return super.execute(invoker, target, HANDLE_REQUEST_METHOD, new Object[] { message }); // NOSONAR
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.handler.MessageProcessor;
|
||||
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilderFactory;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
@@ -118,10 +119,18 @@ public abstract class AbstractMessageProcessingTransformer
|
||||
return (Message<?>) result;
|
||||
}
|
||||
|
||||
AbstractIntegrationMessageBuilder<?> messageBuilder;
|
||||
|
||||
if (result instanceof AbstractIntegrationMessageBuilder<?>) {
|
||||
messageBuilder = (AbstractIntegrationMessageBuilder<?>) result;
|
||||
}
|
||||
else {
|
||||
messageBuilder = getMessageBuilderFactory().withPayload(result);
|
||||
}
|
||||
|
||||
MessageHeaders requestHeaders = message.getHeaders();
|
||||
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(result)
|
||||
return messageBuilder
|
||||
.filterAndCopyHeadersIfAbsent(requestHeaders,
|
||||
this.selectiveHeaderPropagation ? this.notPropagatedHeaders : null)
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.handler.advice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||
import org.springframework.cache.interceptor.CachePutOperation;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.Transformer;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class CacheRequestHandlerAdviceTests {
|
||||
|
||||
private static final String TEST_CACHE = "testCache";
|
||||
|
||||
private static final String TEST_PUT_CACHE = "testPutCache";
|
||||
|
||||
@Autowired
|
||||
private AtomicInteger cachedMethodCounter;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel transformerChannel;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel serviceChannel;
|
||||
|
||||
@Autowired
|
||||
private CacheManager cacheManager;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testCacheRequestHandlerAdvice() {
|
||||
GenericMessage<String> testMessage1 = new GenericMessage<>("foo");
|
||||
this.transformerChannel.send(testMessage1);
|
||||
GenericMessage<String> testMessage2 = new GenericMessage<>("foo");
|
||||
this.transformerChannel.send(testMessage2);
|
||||
this.transformerChannel.send(new GenericMessage<>("foo"));
|
||||
|
||||
assertThat(this.cachedMethodCounter.get()).isEqualTo(1);
|
||||
Cache testCache = cacheManager.getCache(TEST_CACHE);
|
||||
assertThat(testCache).isNotNull();
|
||||
|
||||
ConcurrentMap<Object, Object> nativeCache = (ConcurrentMap<Object, Object>) testCache.getNativeCache();
|
||||
assertThat(nativeCache).hasSize(1);
|
||||
assertThat(nativeCache.values()).element(0).isSameAs(testMessage1);
|
||||
|
||||
this.serviceChannel.send(testMessage1);
|
||||
this.serviceChannel.send(testMessage2);
|
||||
|
||||
assertThat(nativeCache).hasSize(0);
|
||||
|
||||
testCache = cacheManager.getCache(TEST_PUT_CACHE);
|
||||
assertThat(testCache).isNotNull();
|
||||
|
||||
nativeCache = (ConcurrentMap<Object, Object>) testCache.getNativeCache();
|
||||
assertThat(nativeCache).hasSize(1);
|
||||
assertThat(nativeCache.values()).element(0).isSameAs(testMessage2);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheRequestHandlerAdvice cacheAdvice() {
|
||||
CacheRequestHandlerAdvice cacheRequestHandlerAdvice = new CacheRequestHandlerAdvice(TEST_CACHE);
|
||||
cacheRequestHandlerAdvice.setKeyExpressionString("payload");
|
||||
return cacheRequestHandlerAdvice;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AtomicInteger cachedMethodCounter() {
|
||||
return new AtomicInteger();
|
||||
}
|
||||
|
||||
@Transformer(inputChannel = "transformerChannel", outputChannel = "nullChannel", adviceChain = "cacheAdvice")
|
||||
public Object transform(Message<?> message) {
|
||||
cachedMethodCounter().getAndIncrement();
|
||||
return MessageBuilder.fromMessage(message);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheRequestHandlerAdvice cachePutAndEvictAdvice() {
|
||||
CacheRequestHandlerAdvice cacheRequestHandlerAdvice = new CacheRequestHandlerAdvice();
|
||||
cacheRequestHandlerAdvice.setKeyExpressionString("payload");
|
||||
CachePutOperation.Builder cachePutBuilder = new CachePutOperation.Builder();
|
||||
cachePutBuilder.setCacheName(TEST_PUT_CACHE);
|
||||
CacheEvictOperation.Builder cacheEvictBuilder = new CacheEvictOperation.Builder();
|
||||
cacheEvictBuilder.setCacheName(TEST_CACHE);
|
||||
cacheRequestHandlerAdvice.setCacheOperations(cachePutBuilder.build(), cacheEvictBuilder.build());
|
||||
return cacheRequestHandlerAdvice;
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "serviceChannel", outputChannel = "nullChannel",
|
||||
adviceChain = "cachePutAndEvictAdvice")
|
||||
public Message<?> service(Message<?> message) {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,6 +55,7 @@ In addition to providing the general mechanism to apply AOP advice classes, Spri
|
||||
* `RequestHandlerCircuitBreakerAdvice` (described in <<circuit-breaker-advice>>)
|
||||
* `ExpressionEvaluatingRequestHandlerAdvice` (described in <<expression-advice>>)
|
||||
* `RateLimiterRequestHandlerAdvice` (described in <<rate-limiter-advice>>)
|
||||
* `CacheRequestHandlerAdvice` (described in <<cache-advice>>)
|
||||
|
||||
[[retry-advice]]
|
||||
===== Retry Advice
|
||||
@@ -498,6 +499,56 @@ public String handleRequest(String payload) {
|
||||
----
|
||||
====
|
||||
|
||||
[[cache-advice]]
|
||||
===== Caching Advice
|
||||
|
||||
Starting with version 5.2, the `CacheRequestHandlerAdvice` has been introduced.
|
||||
It is based on the caching abstraction in https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache[Spring Framework] and aligned with the concepts and functionality provided by the `@Caching` annotation family.
|
||||
The logic internally is based on the `CacheAspectSupport` extension, where proxying for caching operations is done around the `AbstractReplyProducingMessageHandler.RequestHandler.handleRequestMessage` method with the request `Message<?>` as the argument.
|
||||
This advice can be configured with a SpEL expression or a `Function` to evaluate a cache key.
|
||||
The request `Message<?>` is available as the root object for the SpEL evaluation context, or as the `Function` input argument.
|
||||
By default, the `payload` of the request message is used for the cache key.
|
||||
The `CacheRequestHandlerAdvice` must be configured with `cacheNames`, when a default cache operation is a `CacheableOperation`, or with a set of any arbitrary `CacheOperation` s.
|
||||
Every `CacheOperation` can be configured separately or have shared options, like a `CacheManager`, `CacheResolver` and `CacheErrorHandler`, can be reused from the `CacheRequestHandlerAdvice` configuration.
|
||||
This configuration functionality is similar to Spring Framework's `@CacheConfig` and `@Caching` annotation combination.
|
||||
If a `CacheManager` is not provided, a single bean is resolved by default from the `BeanFactory` in the `CacheAspectSupport`.
|
||||
|
||||
The following example configures two advices with different set of caching operations:
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public CacheRequestHandlerAdvice cacheAdvice() {
|
||||
CacheRequestHandlerAdvice cacheRequestHandlerAdvice = new CacheRequestHandlerAdvice(TEST_CACHE);
|
||||
cacheRequestHandlerAdvice.setKeyExpressionString("payload");
|
||||
return cacheRequestHandlerAdvice;
|
||||
}
|
||||
|
||||
@Transformer(inputChannel = "transformerChannel", outputChannel = "nullChannel", adviceChain = "cacheAdvice")
|
||||
public Object transform(Message<?> message) {
|
||||
...
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheRequestHandlerAdvice cachePutAndEvictAdvice() {
|
||||
CacheRequestHandlerAdvice cacheRequestHandlerAdvice = new CacheRequestHandlerAdvice();
|
||||
cacheRequestHandlerAdvice.setKeyExpressionString("payload");
|
||||
CachePutOperation.Builder cachePutBuilder = new CachePutOperation.Builder();
|
||||
cachePutBuilder.setCacheName(TEST_PUT_CACHE);
|
||||
CacheEvictOperation.Builder cacheEvictBuilder = new CacheEvictOperation.Builder();
|
||||
cacheEvictBuilder.setCacheName(TEST_CACHE);
|
||||
cacheRequestHandlerAdvice.setCacheOperations(cachePutBuilder.build(), cacheEvictBuilder.build());
|
||||
return cacheRequestHandlerAdvice;
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "serviceChannel", outputChannel = "nullChannel",
|
||||
adviceChain = "cachePutAndEvictAdvice")
|
||||
public Message<?> service(Message<?> message) {
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[custom-advice]]
|
||||
==== Custom Advice Classes
|
||||
|
||||
@@ -505,7 +556,7 @@ In addition to the provided advice classes <<advice-classes,described earlier>>,
|
||||
While you can provide any implementation of `org.aopalliance.aop.Advice` (usually `org.aopalliance.intercept.MethodInterceptor`), we generally recommend that you subclass `o.s.i.handler.advice.AbstractRequestHandlerAdvice`.
|
||||
This has the benefit of avoiding the writing of low-level aspect-oriented programming code as well as providing a starting point that is specifically tailored for use in this environment.
|
||||
|
||||
Subclasses need to implement the `doInvoke()`` method, the definition of which follows:
|
||||
Subclasses need to implement the `doInvoke()` method, the definition of which follows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
@@ -737,8 +788,7 @@ On the other hand, if you want all the attempts and any recovery operations (in
|
||||
Sometimes, it is useful to access handler properties from within the advice.
|
||||
For example, most handlers implement `NamedComponent` to let you access the component name.
|
||||
|
||||
The target object can be accessed through the `target` argument (when subclassing `AbstractRequestHandlerAdvice`) or
|
||||
`invocation.getThis()` (when implementing `org.aopalliance.intercept.MethodInterceptor`).
|
||||
The target object can be accessed through the `target` argument (when subclassing `AbstractRequestHandlerAdvice`) or `invocation.getThis()` (when implementing `org.aopalliance.intercept.MethodInterceptor`).
|
||||
|
||||
When the entire handler is advised (such as when the handler does not produce replies or the advice implements `HandleMessageAdvice`), you can cast the target object to an interface, such as `NamedComponent`, as shown in the following example:
|
||||
|
||||
@@ -758,8 +808,7 @@ String componentName = ((NamedComponent) invocation.getThis()).getComponentName(
|
||||
----
|
||||
====
|
||||
|
||||
When only the `handleRequestMessage()` method is advised (in a reply-producing handler), you need to access the
|
||||
full handler, which is an `AbstractReplyProducingMessageHandler`.
|
||||
When only the `handleRequestMessage()` method is advised (in a reply-producing handler), you need to access the full handler, which is an `AbstractReplyProducingMessageHandler`.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
@@ -892,9 +941,8 @@ public IntegrationFlow flow() {
|
||||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: The `IdempotentReceiverInterceptor` is designed only for the `MessageHandler.handleMessage(Message<?>)` method.
|
||||
Starting with version 4.3.1, it implements `HandleMessageAdvice`, with the `AbstractHandleMessageAdvice` as a base class, for better dissociation.
|
||||
See <<handle-message-advice>> for more information.
|
||||
|
||||
====
|
||||
|
||||
@@ -13,6 +13,13 @@ If you are interested in more details, see the Issue Tracker tickets that were r
|
||||
The `RateLimiterRequestHandlerAdvice` is now available for limiting requests rate on handlers.
|
||||
See <<rate-limiter-advice>> for more information.
|
||||
|
||||
[[x5.2-cacheAdvice]]
|
||||
=== Caching Advice Support
|
||||
|
||||
The `CacheRequestHandlerAdvice` is now available for caching request results on handlers.
|
||||
See <<cache-advice>> for more information.
|
||||
|
||||
|
||||
[[x5.2-general]]
|
||||
=== General Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user