Polish "Allow AbstractUrlHandlerMapping to add/remote handlers"
See gh-32064
This commit is contained in:
parent
109d985f89
commit
ad0c488767
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -137,6 +137,116 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
|
||||||
this.lazyInitHandlers = lazyInitHandlers;
|
this.lazyInitHandlers = lazyInitHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the specified handler for the given URL paths.
|
||||||
|
* @param urlPaths the URLs that the bean should be mapped to
|
||||||
|
* @param beanName the name of the handler bean
|
||||||
|
* @throws BeansException if the handler couldn't be registered
|
||||||
|
* @throws IllegalStateException if there is a conflicting handler registered
|
||||||
|
*/
|
||||||
|
public void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
|
||||||
|
Assert.notNull(urlPaths, "URL path array must not be null");
|
||||||
|
for (String urlPath : urlPaths) {
|
||||||
|
registerHandler(urlPath, beanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the specified handler for the given URL path.
|
||||||
|
* @param urlPath the URL the bean should be mapped to
|
||||||
|
* @param handler the handler instance or handler bean name String
|
||||||
|
* (a bean name will automatically be resolved into the corresponding handler bean)
|
||||||
|
* @throws BeansException if the handler couldn't be registered
|
||||||
|
* @throws IllegalStateException if there is a conflicting handler registered
|
||||||
|
*/
|
||||||
|
public void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
|
||||||
|
Assert.notNull(urlPath, "URL path must not be null");
|
||||||
|
Assert.notNull(handler, "Handler object must not be null");
|
||||||
|
Object resolvedHandler = handler;
|
||||||
|
|
||||||
|
// Eagerly resolve handler if referencing singleton via name.
|
||||||
|
if (!this.lazyInitHandlers && handler instanceof String handlerName) {
|
||||||
|
ApplicationContext applicationContext = obtainApplicationContext();
|
||||||
|
if (applicationContext.isSingleton(handlerName)) {
|
||||||
|
resolvedHandler = applicationContext.getBean(handlerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object mappedHandler = this.handlerMap.get(urlPath);
|
||||||
|
if (mappedHandler != null) {
|
||||||
|
if (mappedHandler != resolvedHandler) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
|
||||||
|
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (urlPath.equals("/")) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Root mapping to " + getHandlerDescription(handler));
|
||||||
|
}
|
||||||
|
setRootHandler(resolvedHandler);
|
||||||
|
}
|
||||||
|
else if (urlPath.equals("/*")) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Default mapping to " + getHandlerDescription(handler));
|
||||||
|
}
|
||||||
|
setDefaultHandler(resolvedHandler);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.handlerMap.put(urlPath, resolvedHandler);
|
||||||
|
if (getPatternParser() != null) {
|
||||||
|
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
|
||||||
|
}
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the mapping for the handler registered for the given URL path.
|
||||||
|
* @param urlPath the mapping to remove
|
||||||
|
*/
|
||||||
|
public void unregisterHandler(String urlPath) {
|
||||||
|
Assert.notNull(urlPath, "URL path must not be null");
|
||||||
|
if (urlPath.equals("/")) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Removing root mapping: " + getRootHandler());
|
||||||
|
}
|
||||||
|
setRootHandler(null);
|
||||||
|
}
|
||||||
|
else if (urlPath.equals("/*")) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Removing default mapping: " + getDefaultHandler());
|
||||||
|
}
|
||||||
|
setDefaultHandler(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object mappedHandler = this.handlerMap.get(urlPath);
|
||||||
|
if (mappedHandler == null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("No mapping for [" + urlPath + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Removing mapping \"" + urlPath + "\": " + getHandlerDescription(mappedHandler));
|
||||||
|
}
|
||||||
|
this.handlerMap.remove(urlPath);
|
||||||
|
if (getPatternParser() != null) {
|
||||||
|
this.pathPatternHandlerMap.remove(getPatternParser().parse(urlPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHandlerDescription(Object handler) {
|
||||||
|
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up a handler for the URL path of the given request.
|
* Look up a handler for the URL path of the given request.
|
||||||
* @param request current HTTP request
|
* @param request current HTTP request
|
||||||
|
|
@ -388,112 +498,6 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the specified handler for the given URL paths.
|
|
||||||
* @param urlPaths the URLs that the bean should be mapped to
|
|
||||||
* @param beanName the name of the handler bean
|
|
||||||
* @throws BeansException if the handler couldn't be registered
|
|
||||||
* @throws IllegalStateException if there is a conflicting handler registered
|
|
||||||
*/
|
|
||||||
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
|
|
||||||
Assert.notNull(urlPaths, "URL path array must not be null");
|
|
||||||
for (String urlPath : urlPaths) {
|
|
||||||
registerHandler(urlPath, beanName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the specified handler for the given URL path.
|
|
||||||
* <p>This method may be invoked at runtime after initialization has completed.
|
|
||||||
* @param urlPath the URL the bean should be mapped to
|
|
||||||
* @param handler the handler instance or handler bean name String
|
|
||||||
* (a bean name will automatically be resolved into the corresponding handler bean)
|
|
||||||
* @throws BeansException if the handler couldn't be registered
|
|
||||||
* @throws IllegalStateException if there is a conflicting handler registered
|
|
||||||
*/
|
|
||||||
public void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
|
|
||||||
Assert.notNull(urlPath, "URL path must not be null");
|
|
||||||
Assert.notNull(handler, "Handler object must not be null");
|
|
||||||
Object resolvedHandler = handler;
|
|
||||||
|
|
||||||
// Eagerly resolve handler if referencing singleton via name.
|
|
||||||
if (!this.lazyInitHandlers && handler instanceof String handlerName) {
|
|
||||||
ApplicationContext applicationContext = obtainApplicationContext();
|
|
||||||
if (applicationContext.isSingleton(handlerName)) {
|
|
||||||
resolvedHandler = applicationContext.getBean(handlerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Object mappedHandler = this.handlerMap.get(urlPath);
|
|
||||||
if (mappedHandler != null) {
|
|
||||||
if (mappedHandler != resolvedHandler) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
|
|
||||||
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (urlPath.equals("/")) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Root mapping to " + getHandlerDescription(handler));
|
|
||||||
}
|
|
||||||
setRootHandler(resolvedHandler);
|
|
||||||
}
|
|
||||||
else if (urlPath.equals("/*")) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Default mapping to " + getHandlerDescription(handler));
|
|
||||||
}
|
|
||||||
setDefaultHandler(resolvedHandler);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.handlerMap.put(urlPath, resolvedHandler);
|
|
||||||
if (getPatternParser() != null) {
|
|
||||||
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
|
|
||||||
}
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Un-register the given mapping.
|
|
||||||
* <p>This method may be invoked at runtime after initialization has completed.
|
|
||||||
* @param urlPath the mapping to unregister
|
|
||||||
*/
|
|
||||||
public void unregisterHandler(String urlPath) throws IllegalArgumentException {
|
|
||||||
Assert.notNull(urlPath, "URL path must not be null");
|
|
||||||
Object mappedHandler = this.handlerMap.get(urlPath);
|
|
||||||
if (mappedHandler != null) {
|
|
||||||
if (urlPath.equals("/")) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Unregistered root mapping.");
|
|
||||||
}
|
|
||||||
setRootHandler(null);
|
|
||||||
}
|
|
||||||
else if (urlPath.equals("/*")) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Unregistered default mapping.");
|
|
||||||
}
|
|
||||||
setDefaultHandler(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace("Unregistered mapping \"" + urlPath + "\"");
|
|
||||||
}
|
|
||||||
this.handlerMap.remove(urlPath);
|
|
||||||
if(getPatternParser() != null) {
|
|
||||||
this.pathPatternHandlerMap.remove(getPatternParser().parse(urlPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHandlerDescription(Object handler) {
|
|
||||||
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the handler mappings as a read-only Map, with the registered path
|
* Return the handler mappings as a read-only Map, with the registered path
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2024 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.web.servlet.handler;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.context.support.StaticApplicationContext;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
import static java.util.Map.entry;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AbstractUrlHandlerMapping}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class AbstractUrlHandlerMappingTests {
|
||||||
|
|
||||||
|
private final AbstractUrlHandlerMapping mapping = new AbstractUrlHandlerMapping() {};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerRootHandler() {
|
||||||
|
TestController rootHandler = new TestController();
|
||||||
|
mapping.registerHandler("/", rootHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerDefaultHandler() {
|
||||||
|
TestController defaultHandler = new TestController();
|
||||||
|
mapping.registerHandler("/*", defaultHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerSpecificMapping() {
|
||||||
|
TestController testHandler = new TestController();
|
||||||
|
mapping.registerHandler("/test", testHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(null, null, Map.of("/test", testHandler)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerSpecificMappingWithBeanName() {
|
||||||
|
StaticApplicationContext context = new StaticApplicationContext();
|
||||||
|
context.registerSingleton("controller", TestController.class);
|
||||||
|
mapping.setApplicationContext(context);
|
||||||
|
mapping.registerHandler("/test", "controller");
|
||||||
|
assertThat(mapping.getHandlerMap().get("/test")).isSameAs(context.getBean("controller"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterRootHandler() {
|
||||||
|
TestController rootHandler = new TestController();
|
||||||
|
TestController defaultHandler = new TestController();
|
||||||
|
TestController specificHandler = new TestController();
|
||||||
|
mapping.registerHandler("/", rootHandler);
|
||||||
|
mapping.registerHandler("/*", defaultHandler);
|
||||||
|
mapping.registerHandler("/test", specificHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
|
||||||
|
|
||||||
|
mapping.unregisterHandler("/");
|
||||||
|
assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of("/test", specificHandler)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterDefaultHandler() {
|
||||||
|
TestController rootHandler = new TestController();
|
||||||
|
TestController defaultHandler = new TestController();
|
||||||
|
TestController specificHandler = new TestController();
|
||||||
|
mapping.registerHandler("/", rootHandler);
|
||||||
|
mapping.registerHandler("/*", defaultHandler);
|
||||||
|
mapping.registerHandler("/test", specificHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
|
||||||
|
|
||||||
|
mapping.unregisterHandler("/*");
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of("/test", specificHandler)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterSpecificHandler() {
|
||||||
|
TestController rootHandler = new TestController();
|
||||||
|
TestController defaultHandler = new TestController();
|
||||||
|
TestController specificHandler = new TestController();
|
||||||
|
mapping.registerHandler("/", rootHandler);
|
||||||
|
mapping.registerHandler("/*", defaultHandler);
|
||||||
|
mapping.registerHandler("/test", specificHandler);
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
|
||||||
|
|
||||||
|
mapping.unregisterHandler("/test");
|
||||||
|
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterUnsetRootHandler() {
|
||||||
|
assertThatNoException().isThrownBy(() -> mapping.unregisterHandler("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterUnsetDefaultHandler() {
|
||||||
|
assertThatNoException().isThrownBy(() -> mapping.unregisterHandler("/*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unregisterUnknownHandler() {
|
||||||
|
TestController specificHandler = new TestController();
|
||||||
|
mapping.registerHandler("/test", specificHandler);
|
||||||
|
|
||||||
|
mapping.unregisterHandler("/test/*");
|
||||||
|
assertThat(mapping.getHandlerMap()).containsExactly(entry("/test", specificHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Consumer<AbstractUrlHandlerMapping> hasMappings(@Nullable Object rootHandler,
|
||||||
|
@Nullable Object defaultHandler, Map<String, Object> handlerMap) {
|
||||||
|
return actual -> {
|
||||||
|
assertThat(actual.getRootHandler()).isEqualTo(rootHandler);
|
||||||
|
assertThat(actual.getDefaultHandler()).isEqualTo(defaultHandler);
|
||||||
|
assertThat(actual.getHandlerMap()).containsExactlyEntriesOf(handlerMap);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestController {}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue