mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Configure a JerseyApplicationPath bean for the actuators
This commit also ensures that Jersey-based actuator endpoints are available before the user has configured a `ResourceConfig` bean Fixes gh-15625 Fixes gh-15877
This commit is contained in:
parent
c24f02696f
commit
26da45aa9a
@ -23,7 +23,9 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
@ -40,8 +42,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
|
||||
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration} for Jersey {@link Endpoint} concerns.
|
||||
@ -49,6 +57,7 @@ import org.springframework.context.annotation.Bean;
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @author Michael Simons
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@ManagementContextConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ -57,12 +66,6 @@ import org.springframework.context.annotation.Bean;
|
||||
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
|
||||
class JerseyWebEndpointManagementContextConfiguration {
|
||||
|
||||
@ConditionalOnMissingBean(ResourceConfig.class)
|
||||
@Bean
|
||||
public ResourceConfig resourceConfig() {
|
||||
return new ResourceConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceConfigCustomizer webEndpointRegistrar(
|
||||
WebEndpointsSupplier webEndpointsSupplier,
|
||||
@ -85,4 +88,37 @@ class JerseyWebEndpointManagementContextConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(ResourceConfig.class)
|
||||
@EnableConfigurationProperties(JerseyProperties.class)
|
||||
static class ResourceConfigConfiguration {
|
||||
|
||||
@Bean
|
||||
public ResourceConfig resourceConfig(
|
||||
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
resourceConfigCustomizers.orderedStream()
|
||||
.forEach((customizer) -> customizer.customize(resourceConfig));
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties,
|
||||
ResourceConfig config) {
|
||||
return new DefaultJerseyApplicationPath(properties.getApplicationPath(),
|
||||
config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
|
||||
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers,
|
||||
JerseyApplicationPath jerseyApplicationPath) {
|
||||
return new ServletRegistrationBean<>(
|
||||
new ServletContainer(resourceConfig(resourceConfigCustomizers)),
|
||||
jerseyApplicationPath.getUrlMapping());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,43 +19,108 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link JerseyWebEndpointManagementContextConfiguration}.
|
||||
*
|
||||
* @author Michael Simons
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class JerseyWebEndpointManagementContextConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
|
||||
JerseyWebEndpointManagementContextConfiguration.class));
|
||||
JerseyWebEndpointManagementContextConfiguration.class))
|
||||
.withUserConfiguration(WebEndpointsSupplierConfig.class);
|
||||
|
||||
@Test
|
||||
public void resourceConfigIsAutoConfiguredWhenNeeded() {
|
||||
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class).run(
|
||||
this.runner.run(
|
||||
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingResourceConfigIsUsedWhenAvailable() {
|
||||
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class,
|
||||
ConfigWithResourceConfig.class).run((context) -> {
|
||||
public void jerseyApplicationPathIsAutoConfiguredWhenNeeded() {
|
||||
this.runner.run((context) -> assertThat(context)
|
||||
.hasSingleBean(DefaultJerseyApplicationPath.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jerseyApplicationPathIsConditionalOnMissinBean() {
|
||||
this.runner.withUserConfiguration(ConfigWithJerseyApplicationPath.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JerseyApplicationPath.class);
|
||||
assertThat(context).hasBean("testJerseyApplicationPath");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void servletRegistrationBeanIsAutoConfiguredWhenNeeded() {
|
||||
this.runner.withPropertyValues("spring.jersey.application-path=/jersey")
|
||||
.run((context) -> {
|
||||
ServletRegistrationBean<ServletContainer> bean = context
|
||||
.getBean(ServletRegistrationBean.class);
|
||||
assertThat(bean.getUrlMappings()).containsExactly("/jersey/*");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingResourceConfigBeanShouldNotAutoConfigureRelatedBeans() {
|
||||
this.runner.withUserConfiguration(ConfigWithResourceConfig.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ResourceConfig.class);
|
||||
assertThat(context).doesNotHaveBean(JerseyApplicationPath.class);
|
||||
assertThat(context).doesNotHaveBean(ServletRegistrationBean.class);
|
||||
assertThat(context).hasBean("customResourceConfig");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
|
||||
this.runner.withUserConfiguration(CustomizerConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ResourceConfig.class);
|
||||
ResourceConfig config = context.getBean(ResourceConfig.class);
|
||||
ResourceConfigCustomizer customizer = (ResourceConfigCustomizer) context
|
||||
.getBean("testResourceConfigCustomizer");
|
||||
verify(customizer).customize(config);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceConfigCustomizerBeanIsNotRequired() {
|
||||
this.runner.run(
|
||||
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceConfigCustomizer testResourceConfigCustomizer() {
|
||||
return mock(ResourceConfigCustomizer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class WebEndpointsSupplierConfig {
|
||||
|
||||
@ -76,4 +141,14 @@ public class JerseyWebEndpointManagementContextConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConfigWithJerseyApplicationPath {
|
||||
|
||||
@Bean
|
||||
public JerseyApplicationPath testJerseyApplicationPath() {
|
||||
return mock(JerseyApplicationPath.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,11 +41,22 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
* Integration tests for the Jersey actuator endpoints.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class JerseyEndpointIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void linksAreProvidedToAllEndpointTypes() throws Exception {
|
||||
public void linksAreProvidedToAllEndpointTypes() {
|
||||
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class,
|
||||
ResourceConfigConfiguration.class });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuatorEndpointsWhenUserProvidedResourceConfigBeanNotAvailable() {
|
||||
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class });
|
||||
}
|
||||
|
||||
protected void testJerseyEndpoints(Class[] userConfigurations) {
|
||||
FilteredClassLoader classLoader = new FilteredClassLoader(
|
||||
DispatcherServlet.class);
|
||||
new WebApplicationContextRunner(
|
||||
@ -59,7 +70,7 @@ public class JerseyEndpointIntegrationTests {
|
||||
WebEndpointAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class,
|
||||
BeansEndpointAutoConfiguration.class))
|
||||
.withUserConfiguration(EndpointsConfiguration.class)
|
||||
.withUserConfiguration(userConfigurations)
|
||||
.withPropertyValues("management.endpoints.web.exposure.include:*",
|
||||
"server.port:0")
|
||||
.run((context) -> {
|
||||
@ -88,11 +99,6 @@ public class JerseyEndpointIntegrationTests {
|
||||
@Configuration
|
||||
static class EndpointsConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceConfig testResourceConfig() {
|
||||
return new ResourceConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestControllerEndpoint testControllerEndpoint() {
|
||||
return new TestControllerEndpoint();
|
||||
@ -105,4 +111,14 @@ public class JerseyEndpointIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ResourceConfigConfiguration {
|
||||
|
||||
@Bean
|
||||
ResourceConfig testResourceConfig() {
|
||||
return new ResourceConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRegistration;
|
||||
import javax.ws.rs.ApplicationPath;
|
||||
import javax.ws.rs.ext.ContextResolver;
|
||||
|
||||
import com.fasterxml.jackson.databind.AnnotationIntrospector;
|
||||
@ -51,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.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
@ -60,10 +60,8 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
|
||||
@ -114,15 +112,8 @@ public class JerseyAutoConfiguration implements ServletContextAware {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public JerseyApplicationPath jerseyApplicationPath() {
|
||||
return this::resolveApplicationPath;
|
||||
}
|
||||
|
||||
private String resolveApplicationPath() {
|
||||
if (StringUtils.hasLength(this.jersey.getApplicationPath())) {
|
||||
return this.jersey.getApplicationPath();
|
||||
}
|
||||
return findApplicationPath(AnnotationUtils.findAnnotation(
|
||||
this.config.getApplication().getClass(), ApplicationPath.class));
|
||||
return new DefaultJerseyApplicationPath(this.jersey.getApplicationPath(),
|
||||
this.config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -171,14 +162,6 @@ public class JerseyAutoConfiguration implements ServletContextAware {
|
||||
this.jersey.getInit().forEach(registration::addInitParameter);
|
||||
}
|
||||
|
||||
private static String findApplicationPath(ApplicationPath annotation) {
|
||||
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
|
||||
if (annotation == null) {
|
||||
return "/*";
|
||||
}
|
||||
return annotation.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
String servletRegistrationName = getServletRegistrationName();
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.web.servlet;
|
||||
|
||||
import javax.ws.rs.ApplicationPath;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link JerseyApplicationPath} that derives the path from
|
||||
* {@link JerseyProperties} or the {@code @ApplicationPath} annotation.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class DefaultJerseyApplicationPath implements JerseyApplicationPath {
|
||||
|
||||
private final String applicationPath;
|
||||
|
||||
private final ResourceConfig config;
|
||||
|
||||
public DefaultJerseyApplicationPath(String applicationPath, ResourceConfig config) {
|
||||
this.applicationPath = applicationPath;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return resolveApplicationPath();
|
||||
}
|
||||
|
||||
private String resolveApplicationPath() {
|
||||
if (StringUtils.hasLength(this.applicationPath)) {
|
||||
return this.applicationPath;
|
||||
}
|
||||
return findApplicationPath(AnnotationUtils.findAnnotation(
|
||||
this.config.getApplication().getClass(), ApplicationPath.class));
|
||||
}
|
||||
|
||||
private static String findApplicationPath(ApplicationPath annotation) {
|
||||
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
|
||||
if (annotation == null) {
|
||||
return "/*";
|
||||
}
|
||||
return annotation.value();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user