Skip to content

Commit 2b62897

Browse files
committed
Merge pull request #32064 from awmeinema
* pr/32064: Polish "Allow AbstractUrlHandlerMapping to add/remote handlers" Allow AbstractUrlHandlerMapping to add/remote handlers Closes gh-32064
2 parents a7ceedf + ad0c488 commit 2b62897

File tree

2 files changed

+254
-73
lines changed

2 files changed

+254
-73
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java

+111-73
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -137,6 +137,116 @@ public void setLazyInitHandlers(boolean lazyInitHandlers) {
137137
this.lazyInitHandlers = lazyInitHandlers;
138138
}
139139

140+
/**
141+
* Register the specified handler for the given URL paths.
142+
* @param urlPaths the URLs that the bean should be mapped to
143+
* @param beanName the name of the handler bean
144+
* @throws BeansException if the handler couldn't be registered
145+
* @throws IllegalStateException if there is a conflicting handler registered
146+
*/
147+
public void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
148+
Assert.notNull(urlPaths, "URL path array must not be null");
149+
for (String urlPath : urlPaths) {
150+
registerHandler(urlPath, beanName);
151+
}
152+
}
153+
154+
/**
155+
* Register the specified handler for the given URL path.
156+
* @param urlPath the URL the bean should be mapped to
157+
* @param handler the handler instance or handler bean name String
158+
* (a bean name will automatically be resolved into the corresponding handler bean)
159+
* @throws BeansException if the handler couldn't be registered
160+
* @throws IllegalStateException if there is a conflicting handler registered
161+
*/
162+
public void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
163+
Assert.notNull(urlPath, "URL path must not be null");
164+
Assert.notNull(handler, "Handler object must not be null");
165+
Object resolvedHandler = handler;
166+
167+
// Eagerly resolve handler if referencing singleton via name.
168+
if (!this.lazyInitHandlers && handler instanceof String handlerName) {
169+
ApplicationContext applicationContext = obtainApplicationContext();
170+
if (applicationContext.isSingleton(handlerName)) {
171+
resolvedHandler = applicationContext.getBean(handlerName);
172+
}
173+
}
174+
175+
Object mappedHandler = this.handlerMap.get(urlPath);
176+
if (mappedHandler != null) {
177+
if (mappedHandler != resolvedHandler) {
178+
throw new IllegalStateException(
179+
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
180+
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
181+
}
182+
}
183+
else {
184+
if (urlPath.equals("/")) {
185+
if (logger.isTraceEnabled()) {
186+
logger.trace("Root mapping to " + getHandlerDescription(handler));
187+
}
188+
setRootHandler(resolvedHandler);
189+
}
190+
else if (urlPath.equals("/*")) {
191+
if (logger.isTraceEnabled()) {
192+
logger.trace("Default mapping to " + getHandlerDescription(handler));
193+
}
194+
setDefaultHandler(resolvedHandler);
195+
}
196+
else {
197+
this.handlerMap.put(urlPath, resolvedHandler);
198+
if (getPatternParser() != null) {
199+
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
200+
}
201+
if (logger.isTraceEnabled()) {
202+
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
203+
}
204+
}
205+
}
206+
}
207+
208+
/**
209+
* Remove the mapping for the handler registered for the given URL path.
210+
* @param urlPath the mapping to remove
211+
*/
212+
public void unregisterHandler(String urlPath) {
213+
Assert.notNull(urlPath, "URL path must not be null");
214+
if (urlPath.equals("/")) {
215+
if (logger.isTraceEnabled()) {
216+
logger.trace("Removing root mapping: " + getRootHandler());
217+
}
218+
setRootHandler(null);
219+
}
220+
else if (urlPath.equals("/*")) {
221+
if (logger.isTraceEnabled()) {
222+
logger.trace("Removing default mapping: " + getDefaultHandler());
223+
}
224+
setDefaultHandler(null);
225+
}
226+
else {
227+
Object mappedHandler = this.handlerMap.get(urlPath);
228+
if (mappedHandler == null) {
229+
if (logger.isTraceEnabled()) {
230+
logger.trace("No mapping for [" + urlPath + "]");
231+
}
232+
}
233+
else {
234+
if (logger.isTraceEnabled()) {
235+
logger.trace("Removing mapping \"" + urlPath + "\": " + getHandlerDescription(mappedHandler));
236+
}
237+
this.handlerMap.remove(urlPath);
238+
if (getPatternParser() != null) {
239+
this.pathPatternHandlerMap.remove(getPatternParser().parse(urlPath));
240+
}
241+
}
242+
}
243+
}
244+
245+
private String getHandlerDescription(Object handler) {
246+
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
247+
}
248+
249+
140250
/**
141251
* Look up a handler for the URL path of the given request.
142252
* @param request current HTTP request
@@ -388,78 +498,6 @@ else if (useTrailingSlashMatch()) {
388498
return null;
389499
}
390500

391-
/**
392-
* Register the specified handler for the given URL paths.
393-
* @param urlPaths the URLs that the bean should be mapped to
394-
* @param beanName the name of the handler bean
395-
* @throws BeansException if the handler couldn't be registered
396-
* @throws IllegalStateException if there is a conflicting handler registered
397-
*/
398-
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
399-
Assert.notNull(urlPaths, "URL path array must not be null");
400-
for (String urlPath : urlPaths) {
401-
registerHandler(urlPath, beanName);
402-
}
403-
}
404-
405-
/**
406-
* Register the specified handler for the given URL path.
407-
* @param urlPath the URL the bean should be mapped to
408-
* @param handler the handler instance or handler bean name String
409-
* (a bean name will automatically be resolved into the corresponding handler bean)
410-
* @throws BeansException if the handler couldn't be registered
411-
* @throws IllegalStateException if there is a conflicting handler registered
412-
*/
413-
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
414-
Assert.notNull(urlPath, "URL path must not be null");
415-
Assert.notNull(handler, "Handler object must not be null");
416-
Object resolvedHandler = handler;
417-
418-
// Eagerly resolve handler if referencing singleton via name.
419-
if (!this.lazyInitHandlers && handler instanceof String handlerName) {
420-
ApplicationContext applicationContext = obtainApplicationContext();
421-
if (applicationContext.isSingleton(handlerName)) {
422-
resolvedHandler = applicationContext.getBean(handlerName);
423-
}
424-
}
425-
426-
Object mappedHandler = this.handlerMap.get(urlPath);
427-
if (mappedHandler != null) {
428-
if (mappedHandler != resolvedHandler) {
429-
throw new IllegalStateException(
430-
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
431-
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
432-
}
433-
}
434-
else {
435-
if (urlPath.equals("/")) {
436-
if (logger.isTraceEnabled()) {
437-
logger.trace("Root mapping to " + getHandlerDescription(handler));
438-
}
439-
setRootHandler(resolvedHandler);
440-
}
441-
else if (urlPath.equals("/*")) {
442-
if (logger.isTraceEnabled()) {
443-
logger.trace("Default mapping to " + getHandlerDescription(handler));
444-
}
445-
setDefaultHandler(resolvedHandler);
446-
}
447-
else {
448-
this.handlerMap.put(urlPath, resolvedHandler);
449-
if (getPatternParser() != null) {
450-
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
451-
}
452-
if (logger.isTraceEnabled()) {
453-
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
454-
}
455-
}
456-
}
457-
}
458-
459-
private String getHandlerDescription(Object handler) {
460-
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
461-
}
462-
463501

464502
/**
465503
* Return the handler mappings as a read-only Map, with the registered path
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.servlet.handler;
18+
19+
import java.util.Map;
20+
import java.util.function.Consumer;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.context.support.StaticApplicationContext;
25+
import org.springframework.lang.Nullable;
26+
27+
import static java.util.Map.entry;
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.assertj.core.api.Assertions.assertThatNoException;
30+
31+
/**
32+
* Tests for {@link AbstractUrlHandlerMapping}.
33+
*
34+
* @author Stephane Nicoll
35+
*/
36+
class AbstractUrlHandlerMappingTests {
37+
38+
private final AbstractUrlHandlerMapping mapping = new AbstractUrlHandlerMapping() {};
39+
40+
@Test
41+
void registerRootHandler() {
42+
TestController rootHandler = new TestController();
43+
mapping.registerHandler("/", rootHandler);
44+
assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of()));
45+
}
46+
47+
@Test
48+
void registerDefaultHandler() {
49+
TestController defaultHandler = new TestController();
50+
mapping.registerHandler("/*", defaultHandler);
51+
assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of()));
52+
}
53+
54+
@Test
55+
void registerSpecificMapping() {
56+
TestController testHandler = new TestController();
57+
mapping.registerHandler("/test", testHandler);
58+
assertThat(mapping).satisfies(hasMappings(null, null, Map.of("/test", testHandler)));
59+
}
60+
61+
@Test
62+
void registerSpecificMappingWithBeanName() {
63+
StaticApplicationContext context = new StaticApplicationContext();
64+
context.registerSingleton("controller", TestController.class);
65+
mapping.setApplicationContext(context);
66+
mapping.registerHandler("/test", "controller");
67+
assertThat(mapping.getHandlerMap().get("/test")).isSameAs(context.getBean("controller"));
68+
}
69+
70+
@Test
71+
void unregisterRootHandler() {
72+
TestController rootHandler = new TestController();
73+
TestController defaultHandler = new TestController();
74+
TestController specificHandler = new TestController();
75+
mapping.registerHandler("/", rootHandler);
76+
mapping.registerHandler("/*", defaultHandler);
77+
mapping.registerHandler("/test", specificHandler);
78+
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
79+
80+
mapping.unregisterHandler("/");
81+
assertThat(mapping).satisfies(hasMappings(null, defaultHandler, Map.of("/test", specificHandler)));
82+
}
83+
84+
@Test
85+
void unregisterDefaultHandler() {
86+
TestController rootHandler = new TestController();
87+
TestController defaultHandler = new TestController();
88+
TestController specificHandler = new TestController();
89+
mapping.registerHandler("/", rootHandler);
90+
mapping.registerHandler("/*", defaultHandler);
91+
mapping.registerHandler("/test", specificHandler);
92+
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
93+
94+
mapping.unregisterHandler("/*");
95+
assertThat(mapping).satisfies(hasMappings(rootHandler, null, Map.of("/test", specificHandler)));
96+
}
97+
98+
@Test
99+
void unregisterSpecificHandler() {
100+
TestController rootHandler = new TestController();
101+
TestController defaultHandler = new TestController();
102+
TestController specificHandler = new TestController();
103+
mapping.registerHandler("/", rootHandler);
104+
mapping.registerHandler("/*", defaultHandler);
105+
mapping.registerHandler("/test", specificHandler);
106+
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of("/test", specificHandler)));
107+
108+
mapping.unregisterHandler("/test");
109+
assertThat(mapping).satisfies(hasMappings(rootHandler, defaultHandler, Map.of()));
110+
}
111+
112+
@Test
113+
void unregisterUnsetRootHandler() {
114+
assertThatNoException().isThrownBy(() -> mapping.unregisterHandler("/"));
115+
}
116+
117+
@Test
118+
void unregisterUnsetDefaultHandler() {
119+
assertThatNoException().isThrownBy(() -> mapping.unregisterHandler("/*"));
120+
}
121+
122+
@Test
123+
void unregisterUnknownHandler() {
124+
TestController specificHandler = new TestController();
125+
mapping.registerHandler("/test", specificHandler);
126+
127+
mapping.unregisterHandler("/test/*");
128+
assertThat(mapping.getHandlerMap()).containsExactly(entry("/test", specificHandler));
129+
}
130+
131+
132+
private Consumer<AbstractUrlHandlerMapping> hasMappings(@Nullable Object rootHandler,
133+
@Nullable Object defaultHandler, Map<String, Object> handlerMap) {
134+
return actual -> {
135+
assertThat(actual.getRootHandler()).isEqualTo(rootHandler);
136+
assertThat(actual.getDefaultHandler()).isEqualTo(defaultHandler);
137+
assertThat(actual.getHandlerMap()).containsExactlyEntriesOf(handlerMap);
138+
};
139+
}
140+
141+
private static class TestController {}
142+
143+
}

0 commit comments

Comments
 (0)