From c84b30d4a42bb9d0f7c9f665a1f8aa17fbf43d74 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 2 Jul 2014 17:00:26 +0200 Subject: [PATCH] CachingDestinationResolverProxy for slow target DestinationResolvers Issue: SPR-11939 --- .../core/CachingDestinationResolverProxy.java | 94 +++++++++++++++++++ .../core/CachingDestinationResolverTests.java | 60 ++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java create mode 100644 spring-messaging/src/test/java/org/springframework/messaging/core/CachingDestinationResolverTests.java diff --git a/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java b/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java new file mode 100644 index 0000000000..02f6c9dfd9 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/core/CachingDestinationResolverProxy.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2014 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.messaging.core; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * {@link DestinationResolver} implementation that proxies a target DestinationResolver, + * caching its {@link #resolveDestination} results. Such caching is particularly useful + * if the destination resolving process is expensive (e.g. the destination has to be + * resolved through an external system) and the resolution results are stable anyway. + * + * @author Agim Emruli + * @author Juergen Hoeller + * @since 4.1 + * @see DestinationResolver#resolveDestination + */ +public class CachingDestinationResolverProxy implements DestinationResolver, InitializingBean { + + private final ConcurrentHashMap resolvedDestinationCache = new ConcurrentHashMap(); + + private DestinationResolver targetDestinationResolver; + + + /** + * Create a new CachingDestinationResolverProxy, setting the target DestinationResolver + * through the {@link #setTargetDestinationResolver} bean property. + */ + public CachingDestinationResolverProxy() { + } + + /** + * Create a new CachingDestinationResolverProxy using the given target + * DestinationResolver to actually resolve destinations. + * @param targetDestinationResolver the target DestinationResolver to delegate to + */ + public CachingDestinationResolverProxy(DestinationResolver targetDestinationResolver) { + Assert.notNull(targetDestinationResolver, "Target DestinationResolver must not be null"); + this.targetDestinationResolver = targetDestinationResolver; + } + + + /** + * Set the target DestinationResolver to delegate to. + */ + public void setTargetDestinationResolver(DestinationResolver targetDestinationResolver) { + this.targetDestinationResolver = targetDestinationResolver; + } + + @Override + public void afterPropertiesSet() { + if (this.targetDestinationResolver == null) { + throw new IllegalArgumentException("Property 'targetDestinationResolver' is required"); + } + } + + + /** + * + * Resolves and caches destinations if successfully resolved by the target + * DestinationResolver implementation. + * @param name the destination name to be resolved + * @return the currently resolved destination or an already cached destination + * @throws DestinationResolutionException if the target DestinationResolver + * reports an error during destination resolution + */ + @Override + public D resolveDestination(String name) throws DestinationResolutionException { + D destination = this.resolvedDestinationCache.get(name); + if (destination == null) { + destination = this.targetDestinationResolver.resolveDestination(name); + this.resolvedDestinationCache.putIfAbsent(name, destination); + } + return destination; + } + +} diff --git a/spring-messaging/src/test/java/org/springframework/messaging/core/CachingDestinationResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/core/CachingDestinationResolverTests.java new file mode 100644 index 0000000000..e0fec61324 --- /dev/null +++ b/spring-messaging/src/test/java/org/springframework/messaging/core/CachingDestinationResolverTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2014 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.messaging.core; + +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link CachingDestinationResolverProxy}. + * + * @author Agim Emruli + * @author Juergen Hoeller + */ +public class CachingDestinationResolverTests { + + @Test + public void cachedDestination() { + @SuppressWarnings("unchecked") + DestinationResolver destinationResolver = (DestinationResolver) mock(DestinationResolver.class); + CachingDestinationResolverProxy cachingDestinationResolver = new CachingDestinationResolverProxy(destinationResolver); + + when(destinationResolver.resolveDestination("abcd")).thenReturn("dcba"); + when(destinationResolver.resolveDestination("1234")).thenReturn("4321"); + + assertEquals("dcba", cachingDestinationResolver.resolveDestination("abcd")); + assertEquals("4321", cachingDestinationResolver.resolveDestination("1234")); + assertEquals("4321", cachingDestinationResolver.resolveDestination("1234")); + assertEquals("dcba", cachingDestinationResolver.resolveDestination("abcd")); + + verify(destinationResolver, times(1)).resolveDestination("abcd"); + verify(destinationResolver, times(1)).resolveDestination("1234"); + } + + @Test(expected = IllegalArgumentException.class) + public void noTargetSet() { + CachingDestinationResolverProxy cachingDestinationResolver = new CachingDestinationResolverProxy(); + cachingDestinationResolver.afterPropertiesSet(); + } + + @Test(expected = IllegalArgumentException.class) + public void nullTargetThroughConstructor() { + new CachingDestinationResolverProxy(null); + } + +}