Register classes that implement multiple Servlet interfaces
Closes gh-39056
This commit is contained in:
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -67,7 +68,7 @@ public class ServletContextInitializerBeans extends AbstractCollection<ServletCo
|
||||
/**
|
||||
* Seen bean instances or bean names.
|
||||
*/
|
||||
private final Set<Object> seen = new HashSet<>();
|
||||
private final Seen seen = new Seen();
|
||||
|
||||
private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
|
||||
|
||||
@@ -129,7 +130,7 @@ public class ServletContextInitializerBeans extends AbstractCollection<ServletCo
|
||||
this.initializers.add(type, initializer);
|
||||
if (source != null) {
|
||||
// Mark the underlying source as seen in case it wraps an existing bean
|
||||
this.seen.add(source);
|
||||
this.seen.add(type, source);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
String resourceDescription = getResourceDescription(beanName, beanFactory);
|
||||
@@ -174,7 +175,7 @@ public class ServletContextInitializerBeans extends AbstractCollection<ServletCo
|
||||
for (Entry<String, B> entry : entries) {
|
||||
String beanName = entry.getKey();
|
||||
B bean = entry.getValue();
|
||||
if (this.seen.add(bean)) {
|
||||
if (this.seen.add(type, bean)) {
|
||||
// One that we haven't already seen
|
||||
RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
|
||||
int order = getOrder(bean);
|
||||
@@ -198,17 +199,17 @@ public class ServletContextInitializerBeans extends AbstractCollection<ServletCo
|
||||
}
|
||||
|
||||
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type) {
|
||||
return getOrderedBeansOfType(beanFactory, type, Collections.emptySet());
|
||||
return getOrderedBeansOfType(beanFactory, type, Seen.empty());
|
||||
}
|
||||
|
||||
private <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,
|
||||
Set<?> excludes) {
|
||||
Seen seen) {
|
||||
String[] names = beanFactory.getBeanNamesForType(type, true, false);
|
||||
Map<String, T> map = new LinkedHashMap<>();
|
||||
for (String name : names) {
|
||||
if (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) {
|
||||
if (!seen.contains(type, name) && !ScopedProxyUtils.isScopedTarget(name)) {
|
||||
T bean = beanFactory.getBean(name, type);
|
||||
if (!excludes.contains(bean)) {
|
||||
if (!seen.contains(type, bean)) {
|
||||
map.put(name, bean);
|
||||
}
|
||||
}
|
||||
@@ -310,4 +311,37 @@ public class ServletContextInitializerBeans extends AbstractCollection<ServletCo
|
||||
|
||||
}
|
||||
|
||||
private static final class Seen {
|
||||
|
||||
private final Map<Class<?>, Set<Object>> seen = new HashMap<>();
|
||||
|
||||
boolean add(Class<?> type, Object object) {
|
||||
if (contains(type, object)) {
|
||||
return false;
|
||||
}
|
||||
return this.seen.computeIfAbsent(type, (ignore) -> new HashSet<>()).add(object);
|
||||
}
|
||||
|
||||
boolean contains(Class<?> type, Object object) {
|
||||
if (this.seen.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// If it has been directly seen, or the implemented ServletContextInitializer
|
||||
// has been seen already
|
||||
if (type != ServletContextInitializer.class
|
||||
&& this.seen.getOrDefault(type, Collections.emptySet()).contains(object)) {
|
||||
return true;
|
||||
}
|
||||
if (this.seen.getOrDefault(ServletContextInitializer.class, Collections.emptySet()).contains(object)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Seen empty() {
|
||||
return new Seen();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Tests for {@link ServletContextInitializerBeans}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ServletContextInitializerBeansTests {
|
||||
|
||||
@@ -82,6 +83,26 @@ class ServletContextInitializerBeansTests {
|
||||
.isInstanceOf(HttpSessionIdListener.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void classesThatImplementMultipleInterfacesAreRegisteredForAllOfThem() {
|
||||
load(MultipleInterfacesConfiguration.class);
|
||||
ServletContextInitializerBeans initializerBeans = new ServletContextInitializerBeans(
|
||||
this.context.getBeanFactory());
|
||||
assertThat(initializerBeans).hasSize(3);
|
||||
assertThat(initializerBeans).element(0)
|
||||
.isInstanceOf(ServletRegistrationBean.class)
|
||||
.extracting((initializer) -> ((ServletRegistrationBean<?>) initializer).getServlet())
|
||||
.isInstanceOf(TestServletAndFilterAndListener.class);
|
||||
assertThat(initializerBeans).element(1)
|
||||
.isInstanceOf(FilterRegistrationBean.class)
|
||||
.extracting((initializer) -> ((FilterRegistrationBean<?>) initializer).getFilter())
|
||||
.isInstanceOf(TestServletAndFilterAndListener.class);
|
||||
assertThat(initializerBeans).element(2)
|
||||
.isInstanceOf(ServletListenerRegistrationBean.class)
|
||||
.extracting((initializer) -> ((ServletListenerRegistrationBean<?>) initializer).getListener())
|
||||
.isInstanceOf(TestServletAndFilterAndListener.class);
|
||||
}
|
||||
|
||||
private void load(Class<?>... configuration) {
|
||||
this.context = new AnnotationConfigApplicationContext(configuration);
|
||||
}
|
||||
@@ -106,6 +127,16 @@ class ServletContextInitializerBeansTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MultipleInterfacesConfiguration {
|
||||
|
||||
@Bean
|
||||
TestServletAndFilterAndListener testServletAndFilterAndListener() {
|
||||
return new TestServletAndFilterAndListener();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.web.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletRequestListener;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
|
||||
class TestServletAndFilterAndListener implements Servlet, Filter, ServletRequestListener {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig servletConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletConfig getServletConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
|
||||
throws ServletException, IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user