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
pull/1958/head
Dave Syer 10 years ago
parent 1254508357
commit 5d2d39e87d

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.autoconfigure; package org.springframework.boot.actuate.autoconfigure;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; 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.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; 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.EnvironmentMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
@ -102,6 +104,9 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
@Autowired @Autowired
private ManagementServerProperties managementServerProperties; private ManagementServerProperties managementServerProperties;
@Autowired(required = false)
private List<EndpointHandlerMappingCustomizer> mappingCustomizers;
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException { throws BeansException {
@ -118,6 +123,11 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
if (!disabled) { if (!disabled) {
mapping.setPrefix(this.managementServerProperties.getContextPath()); mapping.setPrefix(this.managementServerProperties.getContextPath());
} }
if (this.mappingCustomizers != null) {
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
customizer.customize(mapping);
}
}
return mapping; return mapping;
} }

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.servlet.Filter; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; 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.ManagementErrorEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
@ -63,6 +65,9 @@ public class EndpointWebMvcChildContextConfiguration {
@Value("${error.path:/error}") @Value("${error.path:/error}")
private String errorPath = "/error"; private String errorPath = "/error";
@Autowired(required = false)
private List<EndpointHandlerMappingCustomizer> mappingCustomizers;
@Configuration @Configuration
protected static class ServerCustomization implements protected static class ServerCustomization implements
EmbeddedServletContainerCustomizer { EmbeddedServletContainerCustomizer {
@ -90,7 +95,7 @@ public class EndpointWebMvcChildContextConfiguration {
} }
// Customize as per the parent context first (so e.g. the access logs go to // Customize as per the parent context first (so e.g. the access logs go to
// the same place) // the same place)
server.customize(container); this.server.customize(container);
// Then reset the error pages // Then reset the error pages
container.setErrorPages(Collections.<ErrorPage> emptySet()); container.setErrorPages(Collections.<ErrorPage> emptySet());
// and add the management-specific bits // and add the management-specific bits
@ -130,6 +135,11 @@ public class EndpointWebMvcChildContextConfiguration {
EndpointHandlerMapping mapping = new EndpointHandlerMapping(set); EndpointHandlerMapping mapping = new EndpointHandlerMapping(set);
// In a child context we definitely want to see the parent endpoints // In a child context we definitely want to see the parent endpoints
mapping.setDetectHandlerMethodsInAncestorContexts(true); mapping.setDetectHandlerMethodsInAncestorContexts(true);
if (this.mappingCustomizers != null) {
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
customizer.customize(mapping);
}
}
return mapping; return mapping;
} }

@ -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);
}

@ -24,11 +24,17 @@ import java.lang.annotation.Target;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.EndpointMvcIntegrationTests.Application; import org.springframework.boot.actuate.autoconfigure.EndpointMvcIntegrationTests.Application;
import org.springframework.boot.actuate.endpoint.Endpoint; 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.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; 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.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.TestRestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; 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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; 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.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -66,12 +76,16 @@ public class EndpointMvcIntegrationTests {
@Value("${local.server.port}") @Value("${local.server.port}")
private int port; private int port;
@Autowired
private TestInterceptor interceptor;
@Test @Test
public void envEndpointNotHidden() { public void envEndpointNotHidden() {
String body = new TestRestTemplate().getForObject("http://localhost:" + this.port String body = new TestRestTemplate().getForObject("http://localhost:" + this.port
+ "/env/user.dir", String.class); + "/env/user.dir", String.class);
assertNotNull(body); assertNotNull(body);
assertTrue("Wrong body: \n" + body, body.contains("spring-boot-actuator")); assertTrue("Wrong body: \n" + body, body.contains("spring-boot-actuator"));
assertEquals(1, this.interceptor.getCount());
} }
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@ -104,6 +118,36 @@ public class EndpointMvcIntegrationTests {
return Collections.singletonMap("foo", (Object) "bar"); 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;
}
} }
} }

@ -20,11 +20,17 @@ import java.io.FileNotFoundException;
import java.net.SocketException; import java.net.SocketException;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset; 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.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.Endpoint; 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.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; 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.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; 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.equalTo;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -122,6 +132,10 @@ public class EndpointWebMvcAutoConfigurationTests {
assertContent("/endpoint", ports.get().server, null); assertContent("/endpoint", ports.get().server, null);
assertContent("/controller", ports.get().management, null); assertContent("/controller", ports.get().management, null);
assertContent("/endpoint", ports.get().management, "endpointoutput"); assertContent("/endpoint", ports.get().management, "endpointoutput");
List<?> interceptors = (List<?>) ReflectionTestUtils.getField(
this.applicationContext.getBean(EndpointHandlerMapping.class),
"interceptors");
assertEquals(1, interceptors.size());
this.applicationContext.close(); this.applicationContext.close();
assertAllClosed(); assertAllClosed();
} }
@ -350,6 +364,38 @@ public class EndpointWebMvcAutoConfigurationTests {
return properties; 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 @Configuration

Loading…
Cancel
Save