From 5d2d39e87d73c5cf975053e3c79260b2c64d3b7e Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 19 Nov 2014 15:21:55 +0000 Subject: [PATCH] Add EndpointHandlerMappingCustomizer callback Users can add @Beans of this type to customize the EndpointHandlerMapping (e.g. add interceptors) even if it is in a child context. Fixes gh-1933 --- .../EndpointWebMvcAutoConfiguration.java | 10 ++++ ...dpointWebMvcChildContextConfiguration.java | 12 ++++- .../mvc/EndpointHandlerMappingCustomizer.java | 28 +++++++++++ .../EndpointMvcIntegrationTests.java | 44 ++++++++++++++++++ .../EndpointWebMvcAutoConfigurationTests.java | 46 +++++++++++++++++++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index a729b676f1..6491fae7a9 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure; import java.io.IOException; +import java.util.List; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -38,6 +39,7 @@ import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; @@ -102,6 +104,9 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, @Autowired private ManagementServerProperties managementServerProperties; + @Autowired(required = false) + private List mappingCustomizers; + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -118,6 +123,11 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, if (!disabled) { mapping.setPrefix(this.managementServerProperties.getContextPath()); } + if (this.mappingCustomizers != null) { + for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { + customizer.customize(mapping); + } + } return mapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index ce432bcb2c..6ae24f8785 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.servlet.Filter; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.actuate.endpoint.mvc.ManagementErrorEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; @@ -63,6 +65,9 @@ public class EndpointWebMvcChildContextConfiguration { @Value("${error.path:/error}") private String errorPath = "/error"; + @Autowired(required = false) + private List mappingCustomizers; + @Configuration protected static class ServerCustomization implements EmbeddedServletContainerCustomizer { @@ -90,7 +95,7 @@ public class EndpointWebMvcChildContextConfiguration { } // Customize as per the parent context first (so e.g. the access logs go to // the same place) - server.customize(container); + this.server.customize(container); // Then reset the error pages container.setErrorPages(Collections. emptySet()); // and add the management-specific bits @@ -130,6 +135,11 @@ public class EndpointWebMvcChildContextConfiguration { EndpointHandlerMapping mapping = new EndpointHandlerMapping(set); // In a child context we definitely want to see the parent endpoints mapping.setDetectHandlerMethodsInAncestorContexts(true); + if (this.mappingCustomizers != null) { + for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { + customizer.customize(mapping); + } + } return mapping; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java new file mode 100644 index 0000000000..cab05bf5f5 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingCustomizer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2014 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.actuate.endpoint.mvc; + +/** + * Callback for customizing the {@link EndpointHandlerMapping} at configuration time. + * + * @author Dave Syer + */ +public interface EndpointHandlerMappingCustomizer { + + void customize(EndpointHandlerMapping mapping); + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java index 804e6f714c..79a6900171 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java @@ -24,11 +24,17 @@ import java.lang.annotation.Target; import java.util.Collections; import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.autoconfigure.EndpointMvcIntegrationTests.Application; import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; @@ -39,6 +45,7 @@ import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.TestRestTemplate; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; @@ -47,7 +54,10 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -66,12 +76,16 @@ public class EndpointMvcIntegrationTests { @Value("${local.server.port}") private int port; + @Autowired + private TestInterceptor interceptor; + @Test public void envEndpointNotHidden() { String body = new TestRestTemplate().getForObject("http://localhost:" + this.port + "/env/user.dir", String.class); assertNotNull(body); assertTrue("Wrong body: \n" + body, body.contains("spring-boot-actuator")); + assertEquals(1, this.interceptor.getCount()); } @Target(ElementType.TYPE) @@ -104,6 +118,36 @@ public class EndpointMvcIntegrationTests { return Collections.singletonMap("foo", (Object) "bar"); } + @Bean + public EndpointHandlerMappingCustomizer mappingCustomizer() { + return new EndpointHandlerMappingCustomizer() { + + @Override + public void customize(EndpointHandlerMapping mapping) { + mapping.setInterceptors(new Object[] { interceptor() }); + } + + }; + } + + @Bean + protected TestInterceptor interceptor() { + return new TestInterceptor(); + } + } + + protected static class TestInterceptor extends HandlerInterceptorAdapter { + private int count = 0; + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, + Object handler, ModelAndView modelAndView) throws Exception { + this.count++; + } + + public int getCount() { + return this.count; + } } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index eb0c7c3a93..3710cd2476 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -20,11 +20,17 @@ import java.io.FileNotFoundException; import java.net.SocketException; import java.net.URI; import java.nio.charset.Charset; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; @@ -50,14 +56,18 @@ import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Controller; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.SocketUtils; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -122,6 +132,10 @@ public class EndpointWebMvcAutoConfigurationTests { assertContent("/endpoint", ports.get().server, null); assertContent("/controller", ports.get().management, null); assertContent("/endpoint", ports.get().management, "endpointoutput"); + List interceptors = (List) ReflectionTestUtils.getField( + this.applicationContext.getBean(EndpointHandlerMapping.class), + "interceptors"); + assertEquals(1, interceptors.size()); this.applicationContext.close(); assertAllClosed(); } @@ -350,6 +364,38 @@ public class EndpointWebMvcAutoConfigurationTests { return properties; } + @Bean + public EndpointHandlerMappingCustomizer mappingCustomizer() { + return new EndpointHandlerMappingCustomizer() { + + @Override + public void customize(EndpointHandlerMapping mapping) { + mapping.setInterceptors(new Object[] { interceptor() }); + } + + }; + } + + @Bean + protected TestInterceptor interceptor() { + return new TestInterceptor(); + } + + protected static class TestInterceptor extends HandlerInterceptorAdapter { + private int count = 0; + + @Override + public void postHandle(HttpServletRequest request, + HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + this.count++; + } + + public int getCount() { + return this.count; + } + } + } @Configuration