Reinstate auto-config of RequestContextFilter with Jersey

Previously, the auto-configuration for both Jersey and WebMvc would auto-configure
a RequestContextFilter bean. In 2.1.0, this led to a startup failure due to the latter
attempting to override the bean defined by the former. In addition to the override there
were also problems with the order of the filter as Jersey uses -1 and MVC uses -105.

To avoid the above-described problems, the auto-configuration of the RequestContextFilter
was removed from JerseyAutoConfiguration in 2.1.1. Unfortunately, the broke
request-scoped beans for those using only Jersey.

This commit attempts to strike a better balance by reintroducing the auto-configuration
of RequestContextFilter in JerseyAutoConfiguration. It will back off if the user defines
their own filter or filter registration. WebMvcAutoConfiguration has been updated to
back off in the same manner. This leaves the potential for ordering problems, but they
are no worse than they were before. Furthermore, the user has the means to correct any
problems by using the various filter ordering properties that are provided for Jersey,
Spring Session, Spring Security, etc.

Closes gh-15376
This commit is contained in:
Andy Wilkinson
2019-02-12 12:50:17 +00:00
parent cc64398800
commit 799ac24da1
4 changed files with 170 additions and 1 deletions

View File

@@ -50,6 +50,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandi
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
@@ -64,6 +65,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.filter.RequestContextFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Jersey.
@@ -109,6 +111,16 @@ public class JerseyAutoConfiguration implements ServletContextAware {
.forEach((customizer) -> customizer.customize(this.config));
}
@Bean
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
public FilterRegistrationBean<RequestContextFilter> requestContextFilter() {
FilterRegistrationBean<RequestContextFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RequestContextFilter());
registration.setOrder(this.jersey.getFilter().getOrder() - 1);
registration.setName("requestContextFilter");
return registration;
}
@Bean
@ConditionalOnMissingBean
public JerseyApplicationPath jerseyApplicationPath() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* Copyright 2012-2019 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.
@@ -401,6 +401,7 @@ public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean({ RequestContextListener.class,
RequestContextFilter.class })
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
public static RequestContextFilter requestContextFilter() {
return new OrderedRequestContextFilter();
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.jersey;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.RequestContextFilter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JerseyAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class JerseyAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JerseyAutoConfiguration.class))
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
.withUserConfiguration(ResourceConfig.class);
@Test
public void requestContextFilterRegistrationIsAutoConfigured() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
FilterRegistrationBean<?> registration = context
.getBean(FilterRegistrationBean.class);
assertThat(registration.getFilter()).isInstanceOf(RequestContextFilter.class);
});
}
@Test
public void whenUserDefinesARequestContextFilterTheAutoConfiguredRegistrationBacksOff() {
this.contextRunner.withUserConfiguration(RequestContextFilterConfiguration.class)
.run((context) -> {
assertThat(context).doesNotHaveBean(FilterRegistrationBean.class);
assertThat(context).hasSingleBean(RequestContextFilter.class);
});
}
@Test
public void whenUserDefinesARequestContextFilterRegistrationTheAutoConfiguredRegistrationBacksOff() {
this.contextRunner
.withUserConfiguration(
RequestContextFilterRegistrationConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).hasBean("customRequestContextFilterRegistration");
});
}
@Configuration
static class ResourceConfigConfiguration {
@Bean
public ResourceConfig resourceConfig() {
return new ResourceConfig();
}
}
@Configuration
static class RequestContextFilterConfiguration {
@Bean
public RequestContextFilter requestContextFilter() {
return new RequestContextFilter();
}
}
@Configuration
static class RequestContextFilterRegistrationConfiguration {
@Bean
public FilterRegistrationBean<RequestContextFilter> customRequestContextFilterRegistration() {
return new FilterRegistrationBean<RequestContextFilter>(
new RequestContextFilter());
}
}
}

View File

@@ -45,6 +45,7 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationCon
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContext;
@@ -73,6 +74,7 @@ import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.FormContentFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.filter.RequestContextFilter;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
@@ -913,6 +915,33 @@ public class WebMvcAutoConfigurationTests {
assertThat(mediaTypes).containsOnly(MediaType.ALL);
}
@Test
public void requestContextFilterIsAutoConfigured() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(RequestContextFilter.class));
}
@Test
public void whenUserDefinesARequestContextFilterTheAutoConfiguredRegistrationBacksOff() {
this.contextRunner.withUserConfiguration(RequestContextFilterConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(RequestContextFilter.class);
assertThat(context).hasBean("customRequestContextFilter");
});
}
@Test
public void whenUserDefinesARequestContextFilterRegistrationTheAutoConfiguredFilterBacksOff() {
this.contextRunner
.withUserConfiguration(
RequestContextFilterRegistrationConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(FilterRegistrationBean.class);
assertThat(context).hasBean("customRequestContextFilterRegistration");
assertThat(context).doesNotHaveBean(RequestContextFilter.class);
});
}
private void assertCacheControl(AssertableWebApplicationContext context) {
Map<String, Object> handlerMap = getHandlerMap(
context.getBean("resourceHandlerMapping", HandlerMapping.class));
@@ -1232,4 +1261,25 @@ public class WebMvcAutoConfigurationTests {
}
@Configuration
static class RequestContextFilterConfiguration {
@Bean
public RequestContextFilter customRequestContextFilter() {
return new RequestContextFilter();
}
}
@Configuration
static class RequestContextFilterRegistrationConfiguration {
@Bean
public FilterRegistrationBean<RequestContextFilter> customRequestContextFilterRegistration() {
return new FilterRegistrationBean<RequestContextFilter>(
new RequestContextFilter());
}
}
}