Update support for using "." as path separator

Issue: SPR-11660
This commit is contained in:
Rossen Stoyanchev 2014-07-14 18:47:51 -04:00
parent 928a466b5d
commit ab2526a586
26 changed files with 405 additions and 254 deletions

View File

@ -68,30 +68,35 @@ public class AntPathMatcher implements PathMatcher {
final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256);
private PathSeparatorPatternCache pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR);
/**
* Create a new AntPathMatcher with the default Ant path separator "/".
* Create a new instance with the {@link #DEFAULT_PATH_SEPARATOR}.
*/
public AntPathMatcher() {
}
/**
* Create a new AntPathMatcher.
* @param pathSeparator the path separator to use
* A convenience alternative constructor to use with a custom path separator.
* @param pathSeparator the path separator to use, must not be {@code null}.
* @since 4.1
*/
public AntPathMatcher(String pathSeparator) {
if(pathSeparator != null) {
this.pathSeparator = pathSeparator;
}
Assert.notNull(pathSeparator, "'pathSeparator' is required");
this.pathSeparator = pathSeparator;
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator);
}
/**
* Set the path separator to use for pattern parsing.
* Default is "/", as in Ant.
*/
public void setPathSeparator(String pathSeparator) {
this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator);
}
/**
@ -447,20 +452,20 @@ public class AntPathMatcher implements PathMatcher {
// /hotels/* + /booking -> /hotels/booking
// /hotels/* + booking -> /hotels/booking
if (pattern1.endsWith(this.pathSeparator + "*")) {
return separatorConcat(pattern1.substring(0, pattern1.length() - 2), pattern2);
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
}
// /hotels/** + /booking -> /hotels/**/booking
// /hotels/** + booking -> /hotels/**/booking
if (pattern1.endsWith(this.pathSeparator + "**")) {
return separatorConcat(pattern1, pattern2);
if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
return concat(pattern1, pattern2);
}
int starDotPos1 = pattern1.indexOf("*.");
if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
// simply concatenate the two patterns
return separatorConcat(pattern1, pattern2);
return concat(pattern1, pattern2);
}
String extension1 = pattern1.substring(starDotPos1 + 1);
int dotPos2 = pattern2.indexOf('.');
@ -470,7 +475,7 @@ public class AntPathMatcher implements PathMatcher {
return fileName2 + extension;
}
private String separatorConcat(String path1, String path2) {
private String concat(String path1, String path2) {
if (path1.endsWith(this.pathSeparator) || path2.startsWith(this.pathSeparator)) {
return path1 + path2;
}
@ -763,4 +768,29 @@ public class AntPathMatcher implements PathMatcher {
}
}
/**
* A simple cache for patterns that depend on the configured path separator.
*/
private static class PathSeparatorPatternCache {
private final String endsOnWildCard;
private final String endsOnDoubleWildCard;
private PathSeparatorPatternCache(String pathSeparator) {
this.endsOnWildCard = pathSeparator + "*";
this.endsOnDoubleWildCard = pathSeparator + "**";
}
public String getEndsOnWildCard() {
return this.endsOnWildCard;
}
public String getEndsOnDoubleWildCard() {
return this.endsOnDoubleWildCard;
}
}
}

View File

@ -607,9 +607,10 @@ public class AntPathMatcherTests {
}
@Test
public void noExtensionHandlingWithDotSeparator() {
public void testExtensionMappingWithDotPathSeparator() {
pathMatcher.setPathSeparator(".");
assertEquals("/*.html.hotel.*", pathMatcher.combine("/*.html", "hotel.*"));
assertEquals("Extension mapping should be disabled with \".\" as path separator",
"/*.html.hotel.*", pathMatcher.combine("/*.html", "hotel.*"));
}
}

View File

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@ -55,51 +56,38 @@ public final class DestinationPatternsMessageCondition
* @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
*/
public DestinationPatternsMessageCondition(String... patterns) {
this(patterns, null, true);
this(patterns, null);
}
/**
* Additional constructor with a customized path matcher.
* Alternative constructor accepting a custom PathMatcher.
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathMatcher the customized path matcher to use with patterns
* @param pathMatcher the PathMatcher to use.
*/
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher) {
this(asList(patterns), pathMatcher, true);
this(asList(patterns), pathMatcher);
}
/**
* Additional constructor with a customized path matcher and a flag specifying if
* the destination patterns should be prepended with "/".
* @param patterns the URL patterns to use; if 0, the condition will match to every request.
* @param pathMatcher the customized path matcher to use with patterns
* @param prependLeadingSlash to specify whether each pattern that is not empty and does not
* start with "/" will be prepended with "/" or not
* @since 4.1
*/
public DestinationPatternsMessageCondition(String[] patterns, PathMatcher pathMatcher,
boolean prependLeadingSlash) {
this(asList(patterns), pathMatcher, prependLeadingSlash);
}
private DestinationPatternsMessageCondition(Collection<String> patterns,
PathMatcher pathMatcher, boolean prependLeadingSlash) {
this.patterns = Collections.unmodifiableSet(initializePatterns(patterns, prependLeadingSlash));
private DestinationPatternsMessageCondition(Collection<String> patterns, PathMatcher pathMatcher) {
this.pathMatcher = (pathMatcher != null) ? pathMatcher : new AntPathMatcher();
this.patterns = Collections.unmodifiableSet(prependLeadingSlash(patterns, this.pathMatcher));
}
private static List<String> asList(String... patterns) {
return patterns != null ? Arrays.asList(patterns) : Collections.<String>emptyList();
}
private static Set<String> initializePatterns(Collection<String> patterns,
boolean prependLeadingSlash) {
private static Set<String> prependLeadingSlash(Collection<String> patterns, PathMatcher pathMatcher) {
if (patterns == null) {
return Collections.emptySet();
}
boolean slashSeparator = pathMatcher.combine("a", "a").equals("a/a");
Set<String> result = new LinkedHashSet<String>(patterns.size());
for (String pattern : patterns) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/") && prependLeadingSlash) {
pattern = "/" + pattern;
if (slashSeparator) {
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
pattern = "/" + pattern;
}
}
result.add(pattern);
}
@ -149,7 +137,7 @@ public final class DestinationPatternsMessageCondition
else {
result.add("");
}
return new DestinationPatternsMessageCondition(result, this.pathMatcher, false);
return new DestinationPatternsMessageCondition(result, this.pathMatcher);
}
/**
@ -184,7 +172,7 @@ public final class DestinationPatternsMessageCondition
}
Collections.sort(matches, this.pathMatcher.getPatternComparator(destination));
return new DestinationPatternsMessageCondition(matches, this.pathMatcher, false);
return new DestinationPatternsMessageCondition(matches, this.pathMatcher);
}
/**

View File

@ -90,27 +90,27 @@ public abstract class AbstractMethodMessageHandler<T>
/**
* Configure one or more prefixes to match to the destinations of handled messages.
* Messages whose destination does not start with one of the configured prefixes
* are ignored. When a destination matches one of the configured prefixes, the
* matching part is removed from destination before performing a lookup for a matching
* message handling method. Prefixes without a trailing slash will have one appended
* automatically.
* <p>By default the list of prefixes is empty in which case all destinations match.
* When this property is configured only messages to destinations matching
* one of the configured prefixes are eligible for handling. When there is a
* match the prefix is removed and only the remaining part of the destination
* is used for method-mapping purposes.
*
* <p>By default no prefixes are configured in which case all messages are
* eligible for handling.
*/
public void setDestinationPrefixes(Collection<String> prefixes) {
this.destinationPrefixes.clear();
if (prefixes != null) {
for (String prefix : prefixes) {
prefix = prefix.trim();
if (!prefix.endsWith("/")) {
prefix += "/";
}
this.destinationPrefixes.add(prefix);
}
}
}
/**
* Return the configured destination prefixes.
*/
public Collection<String> getDestinationPrefixes() {
return this.destinationPrefixes;
}
@ -346,11 +346,11 @@ public abstract class AbstractMethodMessageHandler<T>
protected abstract String getDestination(Message<?> message);
/**
* Find if the given destination matches any of the configured allowed destination
* prefixes and if a match is found return the destination with the prefix removed.
* <p>If no destination prefixes are configured, the destination is returned as is.
* @return the destination to use to find matching message handling methods
* or {@code null} if the destination does not match
* Check whether the given destination (of an incoming message) matches to
* one of the configured destination prefixes and if so return the remaining
* portion of the destination after the matched prefix.
* <p>If there are no matching prefixes, return {@code null}.
* <p>If there are no destination prefixes, return the destination as is.
*/
protected String getLookupDestination(String destination) {
if (destination == null) {
@ -361,7 +361,7 @@ public abstract class AbstractMethodMessageHandler<T>
}
for (String prefix : this.destinationPrefixes) {
if (destination.startsWith(prefix)) {
return destination.substring(prefix.length() - 1);
return destination.substring(prefix.length());
}
}
return null;

View File

@ -93,7 +93,9 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
private ConversionService conversionService = new DefaultFormattingConversionService();
private PathMatcher pathMatcher;
private PathMatcher pathMatcher = new AntPathMatcher();
private boolean slashPathSeparator = true;
private Validator validator;
@ -112,22 +114,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
* @param brokerTemplate a messaging template to send application messages to the broker
*/
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
this(clientInboundChannel, clientOutboundChannel, brokerTemplate, null);
}
/**
* Create an instance of SimpAnnotationMethodMessageHandler with the given
* message channels and broker messaging template.
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for messages to clients (e.g. WebSocket clients)
* @param brokerTemplate a messaging template to send application messages to the broker
* @param pathSeparator the path separator to use with the destination patterns
* @since 4.1
*/
public SimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate,
String pathSeparator) {
MessageChannel clientOutboundChannel, SimpMessageSendingOperations brokerTemplate) {
Assert.notNull(clientInboundChannel, "clientInboundChannel must not be null");
Assert.notNull(clientOutboundChannel, "clientOutboundChannel must not be null");
@ -136,7 +123,6 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
this.clientInboundChannel = clientInboundChannel;
this.clientMessagingTemplate = new SimpMessagingTemplate(clientOutboundChannel);
this.brokerTemplate = brokerTemplate;
this.pathMatcher = new AntPathMatcher(pathSeparator);
Collection<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new StringMessageConverter());
@ -144,6 +130,36 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
this.messageConverter = new CompositeMessageConverter(converters);
}
/**
* {@inheritDoc}
* <p>Destination prefixes are expected to be slash-separated Strings and
* therefore a slash is automatically appended where missing to ensure a
* proper prefix-based match (i.e. matching complete segments).
*
* <p>Note however that the remaining portion of a destination after the
* prefix may use a different separator (e.g. commonly "." in messaging)
* depending on the configured {@code PathMatcher}.
*/
@Override
public void setDestinationPrefixes(Collection<String> prefixes) {
super.setDestinationPrefixes(appendSlashes(prefixes));
}
private static Collection<String> appendSlashes(Collection<String> prefixes) {
if (CollectionUtils.isEmpty(prefixes)) {
return prefixes;
}
Collection<String> result = new ArrayList<String>(prefixes.size());
for (String prefix : prefixes) {
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
}
result.add(prefix);
}
return result;
}
/**
* Configure a {@link MessageConverter} to use to convert the payload of a message
* from serialize form with a specific MIME type to an Object matching the target
@ -189,6 +205,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
this.slashPathSeparator = this.pathMatcher.combine("a", "a").equals("a/a");
}
/**
@ -333,31 +350,31 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
if (messageAnnot != null) {
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot, typeAnnotation == null);
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
return result;
}
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
if (subsribeAnnotation != null) {
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation, typeAnnotation == null);
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation, false).combine(result);
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
return result;
}
return null;
}
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation, boolean prependLeadingSlash) {
private SimpMessageMappingInfo createMessageMappingCondition(MessageMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE,
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
}
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation, boolean prependLeadingSlash) {
private SimpMessageMappingInfo createSubscribeCondition(SubscribeMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.SUBSCRIBE,
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher, prependLeadingSlash));
new DestinationPatternsMessageCondition(annotation.value(), this.pathMatcher));
}
@Override
@ -376,6 +393,27 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
}
@Override
protected String getLookupDestination(String destination) {
if (destination == null) {
return null;
}
if (CollectionUtils.isEmpty(getDestinationPrefixes())) {
return destination;
}
for (String prefix : getDestinationPrefixes()) {
if (destination.startsWith(prefix)) {
if (this.slashPathSeparator) {
return destination.substring(prefix.length() - 1);
}
else {
return destination.substring(prefix.length());
}
}
}
return null;
}
@Override
protected SimpMessageMappingInfo getMatchingMapping(SimpMessageMappingInfo mapping, Message<?> message) {
return mapping.getMatchingCondition(message);

View File

@ -51,6 +51,8 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
private SubscriptionRegistry subscriptionRegistry;
private PathMatcher pathMatcher;
private MessageHeaderInitializer headerInitializer;
@ -58,38 +60,21 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
* Create a SimpleBrokerMessageHandler instance with the given message channels
* and destination prefixes.
*
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param inChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param outChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param brokerChannel the channel for the application to send messages to the broker
*/
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
public SimpleBrokerMessageHandler(SubscribableChannel inChannel, MessageChannel outChannel,
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes) {
this(clientInboundChannel, clientOutboundChannel, brokerChannel, destinationPrefixes, null);
}
/**
* Additional constructor with a customized path matcher.
*
* @param clientInboundChannel the channel for receiving messages from clients (e.g. WebSocket clients)
* @param clientOutboundChannel the channel for sending messages to clients (e.g. WebSocket clients)
* @param brokerChannel the channel for the application to send messages to the broker
* @param pathMatcher the path matcher to use
* @since 4.1
*/
public SimpleBrokerMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel,
SubscribableChannel brokerChannel, Collection<String> destinationPrefixes, PathMatcher pathMatcher) {
super(destinationPrefixes);
Assert.notNull(clientInboundChannel, "'clientInboundChannel' must not be null");
Assert.notNull(clientOutboundChannel, "'clientOutboundChannel' must not be null");
Assert.notNull(inChannel, "'clientInboundChannel' must not be null");
Assert.notNull(outChannel, "'clientOutboundChannel' must not be null");
Assert.notNull(brokerChannel, "'brokerChannel' must not be null");
this.clientInboundChannel = clientInboundChannel;
this.clientOutboundChannel = clientOutboundChannel;
this.clientInboundChannel = inChannel;
this.clientOutboundChannel = outChannel;
this.brokerChannel = brokerChannel;
DefaultSubscriptionRegistry subscriptionRegistry = new DefaultSubscriptionRegistry();
if(pathMatcher != null) {
subscriptionRegistry.setPathMatcher(pathMatcher);
}
this.subscriptionRegistry = subscriptionRegistry;
}
@ -106,15 +91,41 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
return this.brokerChannel;
}
/**
* Configure a custom SubscriptionRegistry to use for storing subscriptions.
*
* <p><strong>Note</strong> that when a custom PathMatcher is configured via
* {@link #setPathMatcher}, if the custom registry is not an instance of
* {@link DefaultSubscriptionRegistry}, the provided PathMatcher is not used
* and must be configured directly on the custom registry.
*/
public void setSubscriptionRegistry(SubscriptionRegistry subscriptionRegistry) {
Assert.notNull(subscriptionRegistry, "SubscriptionRegistry must not be null");
this.subscriptionRegistry = subscriptionRegistry;
initPathMatcherToUse();
}
private void initPathMatcherToUse() {
if (this.pathMatcher != null) {
if (this.subscriptionRegistry instanceof DefaultSubscriptionRegistry) {
((DefaultSubscriptionRegistry) this.subscriptionRegistry).setPathMatcher(this.pathMatcher);
}
}
}
public SubscriptionRegistry getSubscriptionRegistry() {
return this.subscriptionRegistry;
}
/**
* When configured, the given PathMatcher is passed down to the
* SubscriptionRegistry to use for matching destination to subscriptions.
*/
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
initPathMatcherToUse();
}
/**
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
* messages sent to the client outbound channel.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 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.
@ -23,6 +23,7 @@ import java.util.List;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.util.Assert;
/**
@ -66,4 +67,6 @@ public abstract class AbstractBrokerRegistration {
return this.destinationPrefixes;
}
protected abstract AbstractBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel);
}

View File

@ -39,9 +39,9 @@ import org.springframework.messaging.simp.user.UserSessionRegistry;
import org.springframework.messaging.support.AbstractSubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@ -206,17 +206,17 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() {
String defaultSeparator = this.getBrokerRegistry().getDefaultSeparator();
SimpAnnotationMethodMessageHandler handler = new SimpAnnotationMethodMessageHandler(
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate(), defaultSeparator);
clientInboundChannel(), clientOutboundChannel(), brokerMessagingTemplate());
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes());
handler.setMessageConverter(brokerMessageConverter());
handler.setValidator(simpValidator());
AntPathMatcher pathMatcher = new AntPathMatcher(defaultSeparator);
handler.setPathMatcher(pathMatcher);
PathMatcher pathMatcher = this.getBrokerRegistry().getPathMatcher();
if (pathMatcher != null) {
handler.setPathMatcher(pathMatcher);
}
return handler;
}
@ -234,9 +234,7 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
@Bean
public UserDestinationMessageHandler userDestinationMessageHandler() {
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(
clientInboundChannel(), brokerChannel(), userDestinationResolver());
return handler;
return new UserDestinationMessageHandler(clientInboundChannel(), brokerChannel(), userDestinationResolver());
}
@Bean

View File

@ -25,6 +25,7 @@ import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
/**
* A registry for configuring message broker options.
@ -47,9 +48,10 @@ public class MessageBrokerRegistry {
private String userDestinationPrefix;
private PathMatcher pathMatcher;
private ChannelRegistration brokerChannelRegistration = new ChannelRegistration();
private String defaultSeparator;
public MessageBrokerRegistry(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel) {
Assert.notNull(clientInboundChannel);
@ -111,6 +113,31 @@ public class MessageBrokerRegistry {
return this;
}
/**
* Configure the PathMatcher to use to match the destinations of incoming
* messages to {@code @MessageMapping} and {@code @SubscribeMapping} methods.
*
* <p>By default {@link org.springframework.util.AntPathMatcher} is configured.
* However applications may provide an {@code AntPathMatcher} instance
* customized to use "." (commonly used in messaging) instead of "/" as path
* separator or provide a completely different PathMatcher implementation.
*
* <p>Note that the configured PathMatcher is only used for matching the
* portion of the destination after the configured prefix. For example given
* application destination prefix "/app" and destination "/app/price.stock.**",
* the message might be mapped to a controller with "price" and "stock.**"
* as its type and method-level mappings respectively.
*
* <p>When the simple broker is enabled, the PathMatcher configured here is
* also used to match message destinations when brokering messages.
*
* @since 4.1
*/
public MessageBrokerRegistry setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
return this;
}
/**
* Customize the channel used to send messages from the application to the message
* broker. By default messages from the application to the message broker are sent
@ -122,24 +149,14 @@ public class MessageBrokerRegistry {
return this.brokerChannelRegistration;
}
/**
* Customize the default separator used for destination patterns matching/combining.
* It can be used to configure "." as the default separator, since it is used in most
* STOMP broker relay, enabling destination patterns like "/topic/PRICE.STOCK.**".
* <p>The default separator is "/".
*/
public MessageBrokerRegistry defaultSeparator(String defaultSeparator) {
this.defaultSeparator = defaultSeparator;
return this;
}
protected SimpleBrokerMessageHandler getSimpleBroker(SubscribableChannel brokerChannel) {
if ((this.simpleBrokerRegistration == null) && (this.brokerRelayRegistration == null)) {
enableSimpleBroker();
}
if (this.simpleBrokerRegistration != null) {
AntPathMatcher pathMatcher = new AntPathMatcher(this.defaultSeparator);
return this.simpleBrokerRegistration.getMessageHandler(brokerChannel, pathMatcher);
SimpleBrokerMessageHandler handler = this.simpleBrokerRegistration.getMessageHandler(brokerChannel);
handler.setPathMatcher(this.pathMatcher);
return handler;
}
return null;
}
@ -160,12 +177,12 @@ public class MessageBrokerRegistry {
return this.userDestinationPrefix;
}
protected PathMatcher getPathMatcher() {
return this.pathMatcher;
}
protected ChannelRegistration getBrokerChannelRegistration() {
return this.brokerChannelRegistration;
}
protected String getDefaultSeparator() {
return this.defaultSeparator;
}
}

View File

@ -19,28 +19,25 @@ package org.springframework.messaging.simp.config;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler;
import org.springframework.util.PathMatcher;
/**
* Registration class for configuring a {@link SimpleBrokerMessageHandler}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 4.0
*/
public class SimpleBrokerRegistration extends AbstractBrokerRegistration {
public SimpleBrokerRegistration(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, String[] destinationPrefixes) {
super(clientInboundChannel, clientOutboundChannel, destinationPrefixes);
public SimpleBrokerRegistration(SubscribableChannel inChannel, MessageChannel outChannel, String[] prefixes) {
super(inChannel, outChannel, prefixes);
}
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel, PathMatcher pathMatcher) {
@Override
protected SimpleBrokerMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
return new SimpleBrokerMessageHandler(getClientInboundChannel(),
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes(), pathMatcher);
getClientOutboundChannel(), brokerChannel, getDestinationPrefixes());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2012 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,42 +18,34 @@ package org.springframework.messaging.handler;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized.Parameter;
import org.springframework.util.AntPathMatcher;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* Unit tests for {@link DestinationPatternsMessageCondition}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
@RunWith(Parameterized.class)
public class DestinationPatternsMessageConditionTests {
@Parameter(0)
public String pathSeparator;
@Parameters
public static Iterable<Object[]> arguments() {
return Arrays.asList(new Object[][]{{"/"}, {"."}});
}
@Test
public void prependSlash() {
DestinationPatternsMessageCondition c = condition("foo");
assertEquals("/foo", c.getPatterns().iterator().next());
}
@Test
public void prependSlashWithCustomPathSeparator() {
DestinationPatternsMessageCondition c =
new DestinationPatternsMessageCondition(new String[] {"foo"}, new AntPathMatcher("."));
assertEquals("Pre-pending should be disabled when not using '/' as path separator",
"foo", c.getPatterns().iterator().next());
}
// SPR-8255
@Test
@ -65,21 +57,20 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void combineEmptySets() {
DestinationPatternsMessageCondition c1 = condition();
DestinationPatternsMessageCondition c2 = suffixCondition();
DestinationPatternsMessageCondition c2 = condition();
assertEquals(condition(""), c1.combine(c2));
}
@Test
public void combineOnePatternWithEmptySet() {
DestinationPatternsMessageCondition c1 = condition("/type1",
pathSeparator + "type2");
DestinationPatternsMessageCondition c2 = suffixCondition();
DestinationPatternsMessageCondition c1 = condition("/type1", "/type2");
DestinationPatternsMessageCondition c2 = condition();
assertEquals(condition("/type1", pathSeparator + "type2"), c1.combine(c2));
assertEquals(condition("/type1", "/type2"), c1.combine(c2));
c1 = condition();
c2 = suffixCondition("/method1", "/method2");
c2 = condition("/method1", "/method2");
assertEquals(condition("/method1", "/method2"), c1.combine(c2));
}
@ -87,12 +78,10 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void combineMultiplePatterns() {
DestinationPatternsMessageCondition c1 = condition("/t1", "/t2");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "m1",
pathSeparator + "m2");
DestinationPatternsMessageCondition c2 = condition("/m1", "/m2");
assertEquals(
condition("/t1" + pathSeparator + "m1", "/t1" + pathSeparator + "m2",
"/t2" + pathSeparator + "m1", "/t2" + pathSeparator + "m2"), c1.combine(c2));
assertEquals(new DestinationPatternsMessageCondition(
"/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"), c1.combine(c2));
}
@Test
@ -105,40 +94,35 @@ public class DestinationPatternsMessageConditionTests {
@Test
public void matchPattern() {
DestinationPatternsMessageCondition condition = condition(
"/foo" + pathSeparator + "*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo" + pathSeparator + "bar"));
DestinationPatternsMessageCondition condition = condition("/foo/*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
assertNotNull(match);
}
@Test
public void matchSortPatterns() {
DestinationPatternsMessageCondition condition = suffixCondition(
pathSeparator + "**", pathSeparator + "foo" + pathSeparator + "bar",
pathSeparator + "foo" + pathSeparator + "*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo(pathSeparator + "foo" + pathSeparator + "bar"));
DestinationPatternsMessageCondition expected = suffixCondition(
pathSeparator + "foo" + pathSeparator + "bar",
pathSeparator + "foo" + pathSeparator + "*", pathSeparator + "**");
DestinationPatternsMessageCondition condition = condition("/**", "/foo/bar", "/foo/*");
DestinationPatternsMessageCondition match = condition.getMatchingCondition(messageTo("/foo/bar"));
DestinationPatternsMessageCondition expected = condition("/foo/bar", "/foo/*", "/**");
assertEquals(expected, match);
}
@Test
public void compareEqualPatterns() {
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "foo*");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo*");
DestinationPatternsMessageCondition c1 = condition("/foo*");
DestinationPatternsMessageCondition c2 = condition("/foo*");
assertEquals(0, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
assertEquals(0, c1.compareTo(c2, messageTo("/foo")));
}
@Test
public void comparePatternSpecificity() {
DestinationPatternsMessageCondition c1 = suffixCondition(pathSeparator + "fo*");
DestinationPatternsMessageCondition c2 = suffixCondition(pathSeparator + "foo");
DestinationPatternsMessageCondition c1 = condition("/fo*");
DestinationPatternsMessageCondition c2 = condition("/foo");
assertEquals(1, c1.compareTo(c2, messageTo(pathSeparator + "foo")));
assertEquals(1, c1.compareTo(c2, messageTo("/foo")));
}
@Test
@ -156,11 +140,7 @@ public class DestinationPatternsMessageConditionTests {
private DestinationPatternsMessageCondition condition(String... patterns) {
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator));
}
private DestinationPatternsMessageCondition suffixCondition(String... patterns) {
return new DestinationPatternsMessageCondition(patterns, new AntPathMatcher(this.pathSeparator), false);
return new DestinationPatternsMessageCondition(patterns);
}
private Message<?> messageTo(String destination) {

View File

@ -16,6 +16,8 @@
package org.springframework.messaging.simp.annotation.support;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -38,6 +40,7 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
@ -148,7 +151,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
@Test
public void simpScope() {
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
Map<String, Object> map = new ConcurrentHashMap<>();
map.put("name", "value");
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId("session1");
@ -160,6 +163,33 @@ public class SimpAnnotationMethodMessageHandlerTests {
assertEquals("scope", this.testController.method);
}
@Test
public void dotPathSeparator() {
DotPathSeparatorController controller = new DotPathSeparatorController();
this.messageHandler.setPathMatcher(new AntPathMatcher("."));
this.messageHandler.registerHandler(controller);
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId("session1");
headers.setSessionAttributes(new HashMap<>());
headers.setDestination("/app1/pre.foo");
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
this.messageHandler.handleMessage(message);
assertEquals("handleFoo", controller.method);
headers = SimpMessageHeaderAccessor.create();
headers.setSessionId("session1");
headers.setSessionAttributes(new HashMap<>());
headers.setDestination("/app2/pre.foo");
message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
this.messageHandler.handleMessage(message);
assertEquals("handleFoo", controller.method);
}
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
@ -232,6 +262,20 @@ public class SimpAnnotationMethodMessageHandlerTests {
}
}
@Controller
@MessageMapping("pre")
private static class DotPathSeparatorController {
private String method;
@MessageMapping("foo")
public void handleFoo() {
this.method = "handleFoo";
}
}
private static class StringTestValidator implements Validator {
private final String invalidValue;

View File

@ -79,7 +79,7 @@ public class MessageBrokerConfigurationTests {
private AnnotationConfigApplicationContext customChannelContext;
private AnnotationConfigApplicationContext customMatchingContext;
private AnnotationConfigApplicationContext customPathMatcherContext;
@Before
@ -101,9 +101,9 @@ public class MessageBrokerConfigurationTests {
this.customChannelContext.register(CustomChannelConfig.class);
this.customChannelContext.refresh();
this.customMatchingContext = new AnnotationConfigApplicationContext();
this.customMatchingContext.register(CustomMatchingSimpleBrokerConfig.class);
this.customMatchingContext.refresh();
this.customPathMatcherContext = new AnnotationConfigApplicationContext();
this.customPathMatcherContext.register(CustomPathMatcherConfig.class);
this.customPathMatcherContext.refresh();
}
@ -407,17 +407,13 @@ public class MessageBrokerConfigurationTests {
}
@Test
public void customMatching() {
SimpleBrokerMessageHandler brokerHandler = this.customMatchingContext.getBean(SimpleBrokerMessageHandler.class);
DefaultSubscriptionRegistry subscriptionRegistry = (DefaultSubscriptionRegistry)brokerHandler.getSubscriptionRegistry();
AntPathMatcher pathMatcher = (AntPathMatcher)subscriptionRegistry.getPathMatcher();
DirectFieldAccessor accessor = new DirectFieldAccessor(pathMatcher);
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
public void customPathMatcher() {
SimpleBrokerMessageHandler broker = this.customPathMatcherContext.getBean(SimpleBrokerMessageHandler.class);
DefaultSubscriptionRegistry registry = (DefaultSubscriptionRegistry) broker.getSubscriptionRegistry();
assertEquals("a.a", registry.getPathMatcher().combine("a", "a"));
SimpAnnotationMethodMessageHandler messageHandler = customMatchingContext.getBean(SimpAnnotationMethodMessageHandler.class);
pathMatcher = (AntPathMatcher)messageHandler.getPathMatcher();
accessor = new DirectFieldAccessor(pathMatcher);
assertEquals(".", accessor.getPropertyValue("pathSeparator"));
SimpAnnotationMethodMessageHandler handler = this.customPathMatcherContext.getBean(SimpAnnotationMethodMessageHandler.class);
assertEquals("a.a", handler.getPathMatcher().combine("a", "a"));
}
@ -504,11 +500,11 @@ public class MessageBrokerConfigurationTests {
}
@Configuration
static class CustomMatchingSimpleBrokerConfig extends SimpleBrokerConfig {
static class CustomPathMatcherConfig extends SimpleBrokerConfig {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.defaultSeparator(".").enableSimpleBroker("/topic", "/queue");
registry.setPathMatcher(new AntPathMatcher(".")).enableSimpleBroker("/topic", "/queue");
}
}

View File

@ -330,11 +330,11 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
if (simpleBrokerElem != null) {
String prefix = simpleBrokerElem.getAttribute("prefix");
cavs.addIndexedArgumentValue(3, Arrays.asList(StringUtils.tokenizeToStringArray(prefix, ",")));
String defaultSeparator = messageBrokerElement.getAttribute("default-separator");
if (!defaultSeparator.isEmpty()) {
cavs.addIndexedArgumentValue(4, new AntPathMatcher(defaultSeparator));
}
brokerDef = new RootBeanDefinition(SimpleBrokerMessageHandler.class, cavs, null);
if (messageBrokerElement.hasAttribute("path-matcher")) {
brokerDef.getPropertyValues().add("pathMatcher",
new RuntimeBeanReference(messageBrokerElement.getAttribute("path-matcher")));
}
}
else if (brokerRelayElem != null) {
String prefix = brokerRelayElem.getAttribute("prefix");
@ -454,15 +454,13 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
mpvs.add("destinationPrefixes",Arrays.asList(StringUtils.tokenizeToStringArray(appDestPrefix, ",")));
mpvs.add("messageConverter", brokerMessageConverterRef);
RootBeanDefinition annotationMethodMessageHandlerDef =
new RootBeanDefinition(SimpAnnotationMethodMessageHandler.class, cavs, mpvs);
String defaultSeparator = messageBrokerElement.getAttribute("default-separator");
if (!defaultSeparator.isEmpty()) {
annotationMethodMessageHandlerDef.getPropertyValues().add("pathMatcher", new AntPathMatcher(defaultSeparator));
RootBeanDefinition beanDef = new RootBeanDefinition(SimpAnnotationMethodMessageHandler.class, cavs, mpvs);
if (messageBrokerElement.hasAttribute("path-matcher")) {
beanDef.getPropertyValues().add("pathMatcher",
new RuntimeBeanReference(messageBrokerElement.getAttribute("path-matcher")));
}
registerBeanDef(annotationMethodMessageHandlerDef, parserCxt, source);
registerBeanDef(beanDef, parserCxt, source);
}
private RuntimeBeanReference registerUserDestinationResolver(Element messageBrokerElement,

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ Copyright 2002-2013 the original author or authors.
~ 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.
@ -19,12 +19,13 @@
<xsd:schema xmlns="http://www.springframework.org/schema/websocket"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/websocket"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"
schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.1.xsd" />
<xsd:complexType name="mapping">
<xsd:annotation>
@ -687,14 +688,31 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-separator" type="xsd:string">
<xsd:attribute name="path-matcher" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Customize the default separator used for destination patterns matching/combining.
It could be used to configure "." as the default separator, since it is used in most
STOMP broker relay, enabling destination patterns like "/topic/PRICE.STOCK.**".
The default separator is "/".
A reference to the PathMatcher to use to match the destinations of incoming
messages to @MessageMapping and @SubscribeMapping methods.
By default AntPathMatcher is configured.
However applications may provide an AntPathMatcher instance
customized to use "." (commonly used in messaging) instead of "/" as path
separator or provide a completely different PathMatcher implementation.
Note that the configured PathMatcher is only used for matching the
portion of the destination after the configured prefix. For example given
application destination prefix "/app" and destination "/app/price.stock.**",
the message might be mapped to a controller with "price" and "stock.**"
as its type and method-level mappings respectively.
When the simple broker is enabled, the PathMatcher configured here is
also used to match message destinations when brokering messages.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java:org.springframework.util.PathMatcher" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:token">

View File

@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>

View File

@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>

View File

@ -3,7 +3,7 @@
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>
<websocket:stomp-endpoint path="/foo"/>

View File

@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal">
<websocket:stomp-endpoint path="/foo,/bar">

View File

@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker order="2">
<websocket:stomp-endpoint path="/foo">

View File

@ -2,9 +2,11 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/personal" default-separator=".">
<websocket:message-broker application-destination-prefix="/app"
user-destination-prefix="/personal"
path-matcher="pathMatcher">
<!-- message-size=128*1024, send-buffer-size=1024*1024 -->
<websocket:transport message-size="131072" send-timeout="25000" send-buffer-size="1048576" />
@ -22,6 +24,10 @@
</websocket:message-broker>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<property name="pathSeparator" value="." />
</bean>
<bean id="myHandler" class="org.springframework.web.socket.config.TestHandshakeHandler"/>
</beans>

View File

@ -3,7 +3,7 @@
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers order="2">
<websocket:mapping path="/foo" handler="fooHandler"/>

View File

@ -3,7 +3,7 @@
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers >
<websocket:mapping path="/test" handler="testHandler"/>

View File

@ -3,7 +3,7 @@
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/test" handler="testHandler"/>

View File

@ -3,7 +3,7 @@
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd">
http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/foo,/bar" handler="fooHandler"/>

View File

@ -37864,8 +37864,8 @@ options. The endpoint is available for clients to connect to at URL path `/app/p
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/queue", "/topic");
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/queue", "/topic");
}
@Override
@ -38001,7 +38001,7 @@ Below is a simple example to illustrate the flow of messages:
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic/");
registry.enableSimpleBroker("/topic");
}
}
@ -38039,17 +38039,28 @@ kinds of arguments and return values supported.
[[websocket-stomp-handle-annotations]]
==== Annotation Message Handling
The `@MessageMapping` annotation is supported on methods of `@Controller`
as well as on `@RestController`-annotated classes.
It can be used for mapping methods to path-like message destinations. It is also
possible to combine with a type-level `@MessageMapping` for expressing shared
mappings across all annotated methods within a controller.
The `@MessageMapping` annotation is supported on methods of `@Controller` classes.
It can be used for mapping methods to message destinations and can also be combined
with the type-level `@MessageMapping` for expressing shared mappings across all
annotated methods within a controller.
Destination mappings can contain Ant-style patterns (e.g. "/foo*", "/foo/**")
and template variables (e.g. "/foo/{id}"), which can then be accessed via
`@DestinationVariable` method arguments. This should be familiar to Spring MVC
users, in fact the same `AntPathMatcher` is used for matching destinations based
on patterns and for extracting template variables.
By default destination mappings are treated as Ant-style, slash-separated, path
patterns, e.g. "/foo*", "/foo/**". etc. They can also contain template variables,
e.g. "/foo/{id}" that can then be referenced via `@DestinationVariable`-annotated
method arguments.
[NOTE]
====
Although Ant-style, slash-separated, path patterns should feel familiar to web
developers, in message brokers and in messaging it is common to use "." as the
separator, for example in the names of destinations such as topics, queues,
exchanges, etc.
Applications can switch to using "." (dot) instead of "/" (slash) as the separator
for destinations mapped to `@MessageMapping` methods simply by configuring an `AntPathMatcher`
with a customized path separator property. This can be done easily through
the provided Java config and XML namespace.
====
The following method arguments are supported for `@MessageMapping` methods:
@ -38137,6 +38148,21 @@ stores them in memory, and broadcasts messages to connected clients with matchin
destinations. The broker supports path-like destinations, including subscriptions
to Ant-style destination patterns.
[NOTE]
====
Although Ant-style, slash-separated, path patterns should feel familiar to web
developers, in message brokers and in messaging it is common to use "." as the
separator, for example in the names of destinations such as topics, queues,
exchanges, etc.
Applications can switch to using "." (dot) instead of "/" (slash) as the separator
for destinations handled by the broker simply by configuring an `AntPathMatcher`
with a customized path separator property. This can be done easily through
the provided Java config and XML namespace.
====
[[websocket-stomp-handle-broker-relay]]
==== Full-Featured Broker
@ -38168,7 +38194,7 @@ Below is example configuration that enables a full-featured broker:
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic/", "/queue/");
registry.enableStompBrokerRelay("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}