Skip to content

Commit 8f72ca6

Browse files
committed
Use ResourceConfig customization to register endpoints with Jersey
Previously, actuator endpoints were registered with Jersey upon injection of the ResourceConfig bean into a registrar class rather than using a ResourceConfigCustomizer. This was done to fix a problem when running the Actuator on a separate port where the main application context's customizers were also applied to the management context, breaking the singleton contract for those resources. This approach meant that the registration could be performed at any point after the ResourceConfig had been created. When Jersey's configured as a Filter this resulted in the registration failing as the attempt was being made after the Filter lifecyle callbacks which make the ResourceConfig immutable. This commit reworks the endpoint registration to be performed using a ManagementContextResourceConfigCustomizer, a resource config customizer that's only applied to the ResourceConfig that's used by the Actuator. When there's a separate management context, this ResourceConfig is created by the Actuator's auto-configuration and the management context resource config customizers are applied to it during its creation. The main application's customizers are not applied. When the actuator is using the same context as the main application, this ResourceConfig is created by the main application. In this case a ResourceConfigCustomizer is defined that delegates to all ManagementContextResourceConfigCustomizers, allowing them to register the actuator endpoints with the main ResourceConfig. Fixes gh-25262
1 parent 1e65069 commit 8f72ca6

File tree

14 files changed

+358
-51
lines changed

14 files changed

+358
-51
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -24,9 +24,9 @@
2424
import org.glassfish.jersey.server.ResourceConfig;
2525
import org.glassfish.jersey.server.model.Resource;
2626

27-
import org.springframework.beans.factory.ObjectProvider;
2827
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2928
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
29+
import org.springframework.boot.actuate.autoconfigure.web.jersey.ManagementContextResourceConfigCustomizer;
3030
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
3131
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
3232
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@@ -43,7 +43,6 @@
4343
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4444
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
4545
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
46-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
4746
import org.springframework.context.annotation.Bean;
4847
import org.springframework.core.env.Environment;
4948
import org.springframework.util.StringUtils;
@@ -67,14 +66,13 @@ class JerseyWebEndpointManagementContextConfiguration {
6766

6867
@Bean
6968
JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment,
70-
ObjectProvider<ResourceConfig> resourceConfig, WebEndpointsSupplier webEndpointsSupplier,
71-
ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes,
72-
WebEndpointProperties webEndpointProperties) {
69+
WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier,
70+
EndpointMediaTypes endpointMediaTypes, WebEndpointProperties webEndpointProperties) {
7371
String basePath = webEndpointProperties.getBasePath();
7472
boolean shouldRegisterLinks = shouldRegisterLinksMapping(environment, basePath);
7573
shouldRegisterLinksMapping(environment, basePath);
76-
return new JerseyWebEndpointsResourcesRegistrar(resourceConfig.getIfAvailable(), webEndpointsSupplier,
77-
servletEndpointsSupplier, endpointMediaTypes, basePath, shouldRegisterLinks);
74+
return new JerseyWebEndpointsResourcesRegistrar(webEndpointsSupplier, servletEndpointsSupplier,
75+
endpointMediaTypes, basePath, shouldRegisterLinks);
7876
}
7977

8078
private boolean shouldRegisterLinksMapping(Environment environment, String basePath) {
@@ -83,12 +81,9 @@ private boolean shouldRegisterLinksMapping(Environment environment, String baseP
8381
}
8482

8583
/**
86-
* Register endpoints with the {@link ResourceConfig}. The
87-
* {@link ResourceConfigCustomizer} cannot be used because we don't want to apply
84+
* Register endpoints with the {@link ResourceConfig} for the management context.
8885
*/
89-
static class JerseyWebEndpointsResourcesRegistrar {
90-
91-
private final ResourceConfig resourceConfig;
86+
static class JerseyWebEndpointsResourcesRegistrar implements ManagementContextResourceConfigCustomizer {
9287

9388
private final WebEndpointsSupplier webEndpointsSupplier;
9489

@@ -100,33 +95,29 @@ static class JerseyWebEndpointsResourcesRegistrar {
10095

10196
private final boolean shouldRegisterLinks;
10297

103-
JerseyWebEndpointsResourcesRegistrar(ResourceConfig resourceConfig, WebEndpointsSupplier webEndpointsSupplier,
98+
JerseyWebEndpointsResourcesRegistrar(WebEndpointsSupplier webEndpointsSupplier,
10499
ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes,
105100
String basePath, boolean shouldRegisterLinks) {
106-
super();
107-
this.resourceConfig = resourceConfig;
108101
this.webEndpointsSupplier = webEndpointsSupplier;
109102
this.servletEndpointsSupplier = servletEndpointsSupplier;
110103
this.mediaTypes = endpointMediaTypes;
111104
this.basePath = basePath;
112105
this.shouldRegisterLinks = shouldRegisterLinks;
113-
register();
114106
}
115107

116-
private void register() {
117-
// We can't easily use @ConditionalOnBean because @AutoConfigureBefore is
118-
// not an option for management contexts. Instead we manually check if
119-
// the resource config bean exists
120-
if (this.resourceConfig == null) {
121-
return;
122-
}
108+
@Override
109+
public void customize(ResourceConfig config) {
110+
register(config);
111+
}
112+
113+
private void register(ResourceConfig config) {
123114
Collection<ExposableWebEndpoint> webEndpoints = this.webEndpointsSupplier.getEndpoints();
124115
Collection<ExposableServletEndpoint> servletEndpoints = this.servletEndpointsSupplier.getEndpoints();
125116
EndpointLinksResolver linksResolver = getLinksResolver(webEndpoints, servletEndpoints);
126117
EndpointMapping mapping = new EndpointMapping(this.basePath);
127-
JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory();
128-
register(resourceFactory.createEndpointResources(mapping, webEndpoints, this.mediaTypes, linksResolver,
129-
this.shouldRegisterLinks));
118+
Collection<Resource> endpointResources = new JerseyEndpointResourceFactory().createEndpointResources(
119+
mapping, webEndpoints, this.mediaTypes, linksResolver, this.shouldRegisterLinks);
120+
register(endpointResources, config);
130121
}
131122

132123
private EndpointLinksResolver getLinksResolver(Collection<ExposableWebEndpoint> webEndpoints,
@@ -137,8 +128,8 @@ private EndpointLinksResolver getLinksResolver(Collection<ExposableWebEndpoint>
137128
return new EndpointLinksResolver(endpoints, this.basePath);
138129
}
139130

140-
private void register(Collection<Resource> resources) {
141-
this.resourceConfig.registerResources(new HashSet<>(resources));
131+
private void register(Collection<Resource> resources, ResourceConfig config) {
132+
config.registerResources(new HashSet<>(resources));
142133
}
143134

144135
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfiguration.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -18,6 +18,7 @@
1818

1919
import org.glassfish.jersey.server.ResourceConfig;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2223
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -47,4 +48,11 @@ public JerseyApplicationPath jerseyApplicationPath() {
4748
return () -> "/";
4849
}
4950

51+
@Bean
52+
ResourceConfig resourceConfig(ObjectProvider<ManagementContextResourceConfigCustomizer> customizers) {
53+
ResourceConfig resourceConfig = new ResourceConfig();
54+
customizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
55+
return resourceConfig;
56+
}
57+
5058
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyManagementContextConfiguration.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -39,9 +39,4 @@ ServletRegistrationBean<ServletContainer> jerseyServletRegistration(JerseyApplic
3939
jerseyApplicationPath.getUrlMapping());
4040
}
4141

42-
@Bean
43-
ResourceConfig resourceConfig() {
44-
return new ResourceConfig();
45-
}
46-
4742
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseySameManagementContextConfiguration.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -18,17 +18,20 @@
1818

1919
import org.glassfish.jersey.server.ResourceConfig;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2223
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2425
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2728
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
29+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
2830
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
2931
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
3032
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3133
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
3235
import org.springframework.context.annotation.Import;
3336

3437
/**
@@ -39,18 +42,36 @@
3942
* @since 2.1.0
4043
*/
4144
@ManagementContextConfiguration(value = ManagementContextType.SAME, proxyBeanMethods = false)
42-
@Import(JerseyManagementContextConfiguration.class)
4345
@EnableConfigurationProperties(JerseyProperties.class)
44-
@ConditionalOnMissingBean(ResourceConfig.class)
4546
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
4647
@ConditionalOnClass(ResourceConfig.class)
4748
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
4849
public class JerseySameManagementContextConfiguration {
4950

5051
@Bean
51-
@ConditionalOnMissingBean(JerseyApplicationPath.class)
52-
public JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties, ResourceConfig config) {
53-
return new DefaultJerseyApplicationPath(properties.getApplicationPath(), config);
52+
ResourceConfigCustomizer managementResourceConfigCustomizerAdapter(
53+
ObjectProvider<ManagementContextResourceConfigCustomizer> customizers) {
54+
return (config) -> customizers.orderedStream().forEach((customizer) -> customizer.customize(config));
55+
}
56+
57+
@Configuration(proxyBeanMethods = false)
58+
@Import(JerseyManagementContextConfiguration.class)
59+
@ConditionalOnMissingBean(ResourceConfig.class)
60+
class JerseyInfrastructureConfiguration {
61+
62+
@Bean
63+
@ConditionalOnMissingBean(JerseyApplicationPath.class)
64+
JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties, ResourceConfig config) {
65+
return new DefaultJerseyApplicationPath(properties.getApplicationPath(), config);
66+
}
67+
68+
@Bean
69+
ResourceConfig resourceConfig(ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
70+
ResourceConfig resourceConfig = new ResourceConfig();
71+
resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
72+
return resourceConfig;
73+
}
74+
5475
}
5576

5677
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-2021 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.boot.actuate.autoconfigure.web.jersey;
18+
19+
import org.glassfish.jersey.server.ResourceConfig;
20+
21+
/**
22+
* Callback interface that can be implemented by beans wishing to customize Jersey's
23+
* {@link ResourceConfig} in the management context before it is used.
24+
*
25+
* @author Andy Wilkinson
26+
* @since 2.3.10
27+
*/
28+
public interface ManagementContextResourceConfigCustomizer {
29+
30+
/**
31+
* Customize the resource config.
32+
* @param config the {@link ResourceConfig} to customize
33+
*/
34+
void customize(ResourceConfig config);
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2012-2021 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.boot.actuate.autoconfigure.endpoint.web.jersey;
18+
19+
import java.util.Set;
20+
21+
import org.glassfish.jersey.server.ResourceConfig;
22+
import org.glassfish.jersey.server.model.Resource;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
26+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
27+
import org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration;
28+
import org.springframework.boot.autoconfigure.AutoConfigurations;
29+
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
31+
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
32+
import org.springframework.boot.logging.LogLevel;
33+
import org.springframework.boot.test.context.FilteredClassLoader;
34+
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
35+
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.web.servlet.DispatcherServlet;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
42+
/**
43+
* Integration tests for web endpoints running on Jersey.
44+
*
45+
* @author Andy Wilkinson
46+
*/
47+
class JerseyWebEndpointIntegrationTests {
48+
49+
@Test
50+
void whenJerseyIsConfiguredToUseAFilterThenResourceRegistrationSucceeds() {
51+
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
52+
.withConfiguration(AutoConfigurations.of(JerseySameManagementContextConfiguration.class,
53+
JerseyAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
54+
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
55+
JerseyWebEndpointManagementContextConfiguration.class))
56+
.withUserConfiguration(ResourceConfigConfiguration.class)
57+
.withClassLoader(new FilteredClassLoader(DispatcherServlet.class))
58+
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
59+
.withPropertyValues("spring.jersey.type=filter", "server.port=0").run((context) -> {
60+
assertThat(context).hasNotFailed();
61+
Set<Resource> resources = context.getBean(ResourceConfig.class).getResources();
62+
assertThat(resources).hasSize(1);
63+
Resource resource = resources.iterator().next();
64+
assertThat(resource.getPath()).isEqualTo("/actuator");
65+
});
66+
}
67+
68+
@Configuration(proxyBeanMethods = false)
69+
static class ResourceConfigConfiguration {
70+
71+
@Bean
72+
ResourceConfig resourceConfig() {
73+
return new ResourceConfig();
74+
}
75+
76+
}
77+
78+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/jersey/JerseyChildManagementContextConfigurationTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -27,8 +27,12 @@
2727
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
2828
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
2929
import org.springframework.boot.web.servlet.ServletRegistrationBean;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
3032

3133
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.verify;
3236

3337
/**
3438
* Tests for {@link JerseyChildManagementContextConfiguration}.
@@ -78,4 +82,25 @@ void resourceConfigCustomizerBeanIsNotRequired() {
7882
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
7983
}
8084

85+
@Test
86+
void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
87+
this.contextRunner.withUserConfiguration(CustomizerConfiguration.class).run((context) -> {
88+
assertThat(context).hasSingleBean(ResourceConfig.class);
89+
ResourceConfig config = context.getBean(ResourceConfig.class);
90+
ManagementContextResourceConfigCustomizer customizer = context
91+
.getBean(ManagementContextResourceConfigCustomizer.class);
92+
verify(customizer).customize(config);
93+
});
94+
}
95+
96+
@Configuration(proxyBeanMethods = false)
97+
static class CustomizerConfiguration {
98+
99+
@Bean
100+
ManagementContextResourceConfigCustomizer resourceConfigCustomizer() {
101+
return mock(ManagementContextResourceConfigCustomizer.class);
102+
}
103+
104+
}
105+
81106
}

0 commit comments

Comments
 (0)