Refactor the Actuator MVC configuration to allow more customization
There is a new spring.factories entry for org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration which loads extra beans into the MVC config for the Actuator. If the management context is a child context all the beans go in the child (except the Spring Security filter still). A big bonus is that you can add WebConfigurerAdapters to configure static resources etc. A new component called ManagementContextResolver can be used to locate the ApplicationContext for the MVC endpoints. Fixes gh-3345
This commit is contained in:
parent
4187b5d8fb
commit
1e464da248
|
@ -17,8 +17,9 @@
|
|||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
|
@ -33,29 +34,13 @@ import org.springframework.beans.BeansException;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties.Security;
|
||||
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.condition.ConditionalOnManagementMvcContext;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
|
||||
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.LogFileMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
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.web.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
|
||||
|
@ -64,19 +49,17 @@ import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
|||
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
|
||||
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
|
@ -99,75 +82,27 @@ import org.springframework.web.servlet.DispatcherServlet;
|
|||
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ HealthMvcEndpointProperties.class,
|
||||
EndpointCorsProperties.class })
|
||||
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
||||
SmartInitializingSingleton {
|
||||
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, SmartInitializingSingleton {
|
||||
|
||||
private static Log logger = LogFactory.getLog(EndpointWebMvcAutoConfiguration.class);
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private HealthMvcEndpointProperties healthMvcEndpointProperties;
|
||||
|
||||
@Autowired
|
||||
private ManagementServerProperties managementServerProperties;
|
||||
|
||||
@Autowired
|
||||
private EndpointCorsProperties corsProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<EndpointHandlerMappingCustomizer> mappingCustomizers;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointHandlerMapping endpointHandlerMapping() {
|
||||
Set<? extends MvcEndpoint> endpoints = mvcEndpoints().getEndpoints();
|
||||
CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
|
||||
EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
|
||||
corsConfiguration);
|
||||
boolean disabled = ManagementServerPort.get(this.applicationContext) != ManagementServerPort.SAME;
|
||||
mapping.setDisabled(disabled);
|
||||
if (!disabled) {
|
||||
mapping.setPrefix(this.managementServerProperties.getContextPath());
|
||||
}
|
||||
if (this.mappingCustomizers != null) {
|
||||
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
|
||||
customizer.customize(mapping);
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
@ConditionalOnManagementMvcContext
|
||||
@Configuration
|
||||
@Import(EndpointWebMvcImportSelector.class)
|
||||
protected static class EndpointWebMvcConfiguration {
|
||||
}
|
||||
|
||||
private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) {
|
||||
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
|
||||
return null;
|
||||
}
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(properties.getAllowedOrigins());
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
|
||||
configuration.setAllowedHeaders(properties.getAllowedHeaders());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
|
||||
configuration.setAllowedMethods(properties.getAllowedMethods());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
|
||||
configuration.setExposedHeaders(properties.getExposedHeaders());
|
||||
}
|
||||
if (properties.getMaxAge() != null) {
|
||||
configuration.setMaxAge(properties.getMaxAge());
|
||||
}
|
||||
if (properties.getAllowCredentials() != null) {
|
||||
configuration.setAllowCredentials(properties.getAllowCredentials());
|
||||
}
|
||||
return configuration;
|
||||
@Bean
|
||||
public ManagementContextResolver managementContextResolver() {
|
||||
return new ManagementContextResolver(this.applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,53 +122,6 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
|||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MvcEndpoints mvcEndpoints() {
|
||||
return new MvcEndpoints();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(EnvironmentEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("env")
|
||||
public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
|
||||
return new EnvironmentMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("health")
|
||||
public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) {
|
||||
Security security = this.managementServerProperties.getSecurity();
|
||||
boolean secure = (security != null && security.isEnabled());
|
||||
HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate, secure);
|
||||
if (this.healthMvcEndpointProperties.getMapping() != null) {
|
||||
healthMvcEndpoint.addStatusMapping(this.healthMvcEndpointProperties
|
||||
.getMapping());
|
||||
}
|
||||
return healthMvcEndpoint;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MetricsEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("metrics")
|
||||
public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
|
||||
return new MetricsMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledEndpoint("logfile")
|
||||
public LogFileMvcEndpoint logfileMvcEndpoint() {
|
||||
return new LogFileMvcEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(ShutdownEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
|
||||
public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
|
||||
return new ShutdownMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
private void createChildManagementContext() {
|
||||
|
||||
final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||
|
@ -241,13 +129,16 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
|||
childContext.setNamespace("management");
|
||||
childContext.setId(this.applicationContext.getId() + ":management");
|
||||
|
||||
List<Class<?>> configurations = new ArrayList<Class<?>>();
|
||||
configurations.addAll(Arrays.<Class<?>> asList(
|
||||
EndpointWebMvcChildContextConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class));
|
||||
// Register the ManagementServerChildContextConfiguration first followed
|
||||
// by various specific AutoConfiguration classes. NOTE: The child context
|
||||
// is intentionally not completely auto-configured.
|
||||
childContext.register(EndpointWebMvcChildContextConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class);
|
||||
childContext.register(configurations.toArray(new Class<?>[0]));
|
||||
|
||||
// Ensure close on the parent also closes the child
|
||||
if (this.applicationContext instanceof ConfigurableApplicationContext) {
|
||||
|
@ -261,6 +152,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
|||
}
|
||||
});
|
||||
}
|
||||
managementContextResolver().setApplicationContext(childContext);
|
||||
try {
|
||||
childContext.refresh();
|
||||
}
|
||||
|
@ -375,4 +267,31 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
|
|||
|
||||
}
|
||||
|
||||
public static class ManagementContextResolver {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public ManagementContextResolver(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public MvcEndpoints getMvcEndpoints() {
|
||||
try {
|
||||
return applicationContext.getBean(MvcEndpoints.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,12 +16,13 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
@ -33,7 +34,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter;
|
||||
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;
|
||||
|
@ -41,8 +41,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
|
||||
|
@ -50,13 +50,18 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomi
|
|||
import org.springframework.boot.context.embedded.ErrorPage;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.HandlerAdapter;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Configuration triggered from {@link EndpointWebMvcAutoConfiguration} when a new
|
||||
|
@ -75,9 +80,17 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
@Value("${error.path:/error}")
|
||||
private String errorPath = "/error";
|
||||
|
||||
@Autowired
|
||||
private ManagementServerProperties managementServerProperties;
|
||||
|
||||
@Configuration
|
||||
@Import(EndpointWebMvcImportSelector.class)
|
||||
protected static class EndpointWebMvcConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class ServerCustomization implements
|
||||
EmbeddedServletContainerCustomizer, Ordered {
|
||||
EmbeddedServletContainerCustomizer, Ordered {
|
||||
|
||||
@Value("${error.path:/error}")
|
||||
private String errorPath = "/error";
|
||||
|
@ -113,13 +126,12 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
// and add the management-specific bits
|
||||
container.setPort(this.managementServerProperties.getPort());
|
||||
container.setAddress(this.managementServerProperties.getAddress());
|
||||
container.setContextPath(this.managementServerProperties.getContextPath());
|
||||
container.addErrorPages(new ErrorPage(this.errorPath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
|
||||
public DispatcherServlet dispatcherServlet() {
|
||||
DispatcherServlet dispatcherServlet = new DispatcherServlet();
|
||||
|
||||
|
@ -132,11 +144,132 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
return dispatcherServlet;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerAdapter handlerAdapter(HttpMessageConverters converters) {
|
||||
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
|
||||
adapter.setMessageConverters(converters.getConverters());
|
||||
return adapter;
|
||||
@Configuration(DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
|
||||
@EnableWebMvc
|
||||
public static class CompositeHandlerMapping implements HandlerMapping {
|
||||
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
private List<HandlerMapping> mappings;
|
||||
|
||||
@Override
|
||||
public HandlerExecutionChain getHandler(HttpServletRequest request)
|
||||
throws Exception {
|
||||
if (this.mappings == null) {
|
||||
this.mappings = extractMappings();
|
||||
}
|
||||
for (HandlerMapping mapping : this.mappings) {
|
||||
HandlerExecutionChain handler = mapping.getHandler(request);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<HandlerMapping> extractMappings() {
|
||||
List<HandlerMapping> list = new ArrayList<HandlerMapping>();
|
||||
list.addAll(this.beanFactory.getBeansOfType(HandlerMapping.class).values());
|
||||
list.remove(this);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(DispatcherServlet.HANDLER_ADAPTER_BEAN_NAME)
|
||||
public static class CompositeHandlerAdapter implements HandlerAdapter {
|
||||
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
private List<HandlerAdapter> adapters;
|
||||
|
||||
private List<HandlerAdapter> extractAdapters() {
|
||||
List<HandlerAdapter> list = new ArrayList<HandlerAdapter>();
|
||||
list.addAll(this.beanFactory.getBeansOfType(HandlerAdapter.class).values());
|
||||
list.remove(this);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Object handler) {
|
||||
if (this.adapters == null) {
|
||||
this.adapters = extractAdapters();
|
||||
}
|
||||
for (HandlerAdapter mapping : this.adapters) {
|
||||
if (mapping.supports(handler)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView handle(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler) throws Exception {
|
||||
if (this.adapters == null) {
|
||||
this.adapters = extractAdapters();
|
||||
}
|
||||
for (HandlerAdapter mapping : this.adapters) {
|
||||
if (mapping.supports(handler)) {
|
||||
return mapping.handle(request, response, handler);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified(HttpServletRequest request, Object handler) {
|
||||
if (this.adapters == null) {
|
||||
this.adapters = extractAdapters();
|
||||
}
|
||||
for (HandlerAdapter mapping : this.adapters) {
|
||||
if (mapping.supports(handler)) {
|
||||
return mapping.getLastModified(request, handler);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(DispatcherServlet.HANDLER_EXCEPTION_RESOLVER_BEAN_NAME)
|
||||
public static class CompositeHandlerExceptionResolver implements
|
||||
HandlerExceptionResolver {
|
||||
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
private List<HandlerExceptionResolver> resolvers;
|
||||
|
||||
private List<HandlerExceptionResolver> extractResolvers() {
|
||||
List<HandlerExceptionResolver> list = new ArrayList<HandlerExceptionResolver>();
|
||||
list.addAll(this.beanFactory.getBeansOfType(HandlerExceptionResolver.class)
|
||||
.values());
|
||||
list.remove(this);
|
||||
AnnotationAwareOrderComparator.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveException(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler, Exception ex) {
|
||||
if (this.resolvers == null) {
|
||||
this.resolvers = extractResolvers();
|
||||
}
|
||||
for (HandlerExceptionResolver mapping : this.resolvers) {
|
||||
ModelAndView mav = mapping.resolveException(request, response, handler,
|
||||
ex);
|
||||
if (mav != null) {
|
||||
return mav;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -158,24 +291,12 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
@ConditionalOnMissingClass("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter")
|
||||
protected static class EndpointHandlerMappingConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<EndpointHandlerMappingCustomizer> mappingCustomizers;
|
||||
|
||||
@Bean
|
||||
public HandlerMapping handlerMapping(MvcEndpoints endpoints,
|
||||
ListableBeanFactory beanFactory) {
|
||||
Set<MvcEndpoint> set = new HashSet<MvcEndpoint>(endpoints.getEndpoints());
|
||||
set.addAll(beanFactory.getBeansOfType(MvcEndpoint.class).values());
|
||||
EndpointHandlerMapping mapping = new EndpointHandlerMapping(set);
|
||||
@Autowired
|
||||
public void handlerMapping(MvcEndpoints endpoints,
|
||||
ListableBeanFactory beanFactory, EndpointHandlerMapping mapping) {
|
||||
// In a child context we definitely want to see the parent endpoints
|
||||
mapping.setDetectHandlerMethodsInAncestorContexts(true);
|
||||
postProcessMapping(beanFactory, mapping);
|
||||
if (this.mappingCustomizers != null) {
|
||||
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
|
||||
customizer.customize(mapping);
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +317,7 @@ public class EndpointWebMvcChildContextConfiguration {
|
|||
@Configuration
|
||||
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
|
||||
protected static class SecureEndpointHandlerMappingConfiguration extends
|
||||
EndpointHandlerMappingConfiguration {
|
||||
EndpointHandlerMappingConfiguration {
|
||||
|
||||
@Override
|
||||
protected void postProcessMapping(ListableBeanFactory beanFactory,
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 2015 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.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties.Security;
|
||||
import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
|
||||
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.LogFileMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ HealthMvcEndpointProperties.class,
|
||||
EndpointCorsProperties.class })
|
||||
public class EndpointWebMvcConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private HealthMvcEndpointProperties healthMvcEndpointProperties;
|
||||
|
||||
@Autowired
|
||||
private ManagementServerProperties managementServerProperties;
|
||||
|
||||
@Autowired
|
||||
private EndpointCorsProperties corsProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<EndpointHandlerMappingCustomizer> mappingCustomizers;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public EndpointHandlerMapping endpointHandlerMapping() {
|
||||
Set<? extends MvcEndpoint> endpoints = mvcEndpoints().getEndpoints();
|
||||
CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
|
||||
EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
|
||||
corsConfiguration);
|
||||
boolean disabled = this.managementServerProperties.getPort()!=null && this.managementServerProperties.getPort()==-1;
|
||||
mapping.setDisabled(disabled);
|
||||
if (!disabled) {
|
||||
mapping.setPrefix(this.managementServerProperties.getContextPath());
|
||||
}
|
||||
if (this.mappingCustomizers != null) {
|
||||
for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) {
|
||||
customizer.customize(mapping);
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) {
|
||||
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
|
||||
return null;
|
||||
}
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(properties.getAllowedOrigins());
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
|
||||
configuration.setAllowedHeaders(properties.getAllowedHeaders());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
|
||||
configuration.setAllowedMethods(properties.getAllowedMethods());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
|
||||
configuration.setExposedHeaders(properties.getExposedHeaders());
|
||||
}
|
||||
if (properties.getMaxAge() != null) {
|
||||
configuration.setMaxAge(properties.getMaxAge());
|
||||
}
|
||||
if (properties.getAllowCredentials() != null) {
|
||||
configuration.setAllowCredentials(properties.getAllowCredentials());
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MvcEndpoints mvcEndpoints() {
|
||||
return new MvcEndpoints();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(EnvironmentEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("env")
|
||||
public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
|
||||
return new EnvironmentMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("health")
|
||||
public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) {
|
||||
Security security = this.managementServerProperties.getSecurity();
|
||||
boolean secure = (security != null && security.isEnabled());
|
||||
HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate, secure);
|
||||
if (this.healthMvcEndpointProperties.getMapping() != null) {
|
||||
healthMvcEndpoint.addStatusMapping(this.healthMvcEndpointProperties
|
||||
.getMapping());
|
||||
}
|
||||
return healthMvcEndpoint;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MetricsEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("metrics")
|
||||
public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
|
||||
return new MetricsMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledEndpoint("logfile")
|
||||
public LogFileMvcEndpoint logfileMvcEndpoint() {
|
||||
return new LogFileMvcEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(ShutdownEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
|
||||
public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
|
||||
return new ShutdownMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.annotation.DeferredImportSelector;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
/**
|
||||
* Selects configuration classes for the Actuator MVC endpoints. Customize the MVC
|
||||
* endpoints by adding an entries to <code>/META-INF/spring.factories</code> under the
|
||||
* {@link EndpointWebMvcConfiguration} key.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EndpointWebMvcImportSelector implements DeferredImportSelector,
|
||||
BeanClassLoaderAware {
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata metadata) {
|
||||
// Find all possible auto configuration classes, filtering duplicates
|
||||
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
|
||||
SpringFactoriesLoader.loadFactoryNames(EndpointWebMvcConfiguration.class,
|
||||
this.beanClassLoader)));
|
||||
return factories.toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
beanClassLoader = classLoader;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,12 +18,15 @@ package org.springframework.boot.actuate.autoconfigure;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration.ManagementContextResolver;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||
|
@ -45,6 +48,7 @@ import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConf
|
|||
import org.springframework.boot.autoconfigure.web.ErrorController;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
@ -61,6 +65,10 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -209,7 +217,10 @@ public class ManagementSecurityAutoConfiguration {
|
|||
@Autowired
|
||||
private ManagementServerProperties management;
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
private ManagementContextResolver contextResolver;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ServerProperties server;
|
||||
|
||||
@Autowired(required = false)
|
||||
|
@ -223,19 +234,17 @@ public class ManagementSecurityAutoConfiguration {
|
|||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// secure endpoints
|
||||
String[] paths = getEndpointPaths(this.endpointHandlerMapping);
|
||||
if (paths.length > 0 && this.management.getSecurity().isEnabled()) {
|
||||
RequestMatcher matcher = getRequestMatcher();
|
||||
if (matcher != null) {
|
||||
// Always protect them if present
|
||||
if (this.security.isRequireSsl()) {
|
||||
http.requiresChannel().anyRequest().requiresSecure();
|
||||
}
|
||||
AuthenticationEntryPoint entryPoint = entryPoint();
|
||||
http.exceptionHandling().authenticationEntryPoint(entryPoint);
|
||||
paths = this.server.getPathsArray(paths);
|
||||
http.requestMatchers().antMatchers(paths);
|
||||
String[] endpointPaths = this.server.getPathsArray(getEndpointPaths(
|
||||
this.endpointHandlerMapping, false));
|
||||
configureAuthorizeRequests(endpointPaths, http.authorizeRequests());
|
||||
http.requestMatcher(matcher);
|
||||
configureAuthorizeRequests(new EndpointPathRequestMatcher(false),
|
||||
http.authorizeRequests());
|
||||
http.httpBasic().authenticationEntryPoint(entryPoint);
|
||||
// No cookies for management endpoints by default
|
||||
http.csrf().disable();
|
||||
|
@ -246,6 +255,19 @@ public class ManagementSecurityAutoConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
private RequestMatcher getRequestMatcher() {
|
||||
if (!this.management.getSecurity().isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
String path = management.getContextPath();
|
||||
if (StringUtils.hasText(path)) {
|
||||
AntPathRequestMatcher matcher = new AntPathRequestMatcher(
|
||||
server.getPath(path) + "/**");
|
||||
return matcher;
|
||||
}
|
||||
return new EndpointPathRequestMatcher();
|
||||
}
|
||||
|
||||
private AuthenticationEntryPoint entryPoint() {
|
||||
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
|
||||
entryPoint.setRealmName(this.security.getBasic().getRealm());
|
||||
|
@ -253,12 +275,55 @@ public class ManagementSecurityAutoConfiguration {
|
|||
}
|
||||
|
||||
private void configureAuthorizeRequests(
|
||||
String[] endpointPaths,
|
||||
RequestMatcher permitAllMatcher,
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests) {
|
||||
requests.antMatchers(endpointPaths).permitAll();
|
||||
requests.requestMatchers(permitAllMatcher).permitAll();
|
||||
requests.anyRequest().hasRole(this.management.getSecurity().getRole());
|
||||
}
|
||||
|
||||
private final class EndpointPathRequestMatcher implements RequestMatcher {
|
||||
|
||||
private boolean sensitive;
|
||||
|
||||
private RequestMatcher delegate;
|
||||
|
||||
public EndpointPathRequestMatcher(boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
public EndpointPathRequestMatcher() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
if (endpointHandlerMapping == null && contextResolver != null) {
|
||||
ApplicationContext context = contextResolver.getApplicationContext();
|
||||
if (context != null
|
||||
&& context.getBeanNamesForType(EndpointHandlerMapping.class).length > 0) {
|
||||
endpointHandlerMapping = context
|
||||
.getBean(EndpointHandlerMapping.class);
|
||||
}
|
||||
}
|
||||
if (endpointHandlerMapping == null) {
|
||||
endpointHandlerMapping = new EndpointHandlerMapping(
|
||||
Collections.<MvcEndpoint> emptySet());
|
||||
}
|
||||
if (delegate == null) {
|
||||
List<RequestMatcher> pathMatchers = new ArrayList<RequestMatcher>();
|
||||
String[] paths = !sensitive ? getEndpointPaths(
|
||||
endpointHandlerMapping, false)
|
||||
: getEndpointPaths(endpointHandlerMapping);
|
||||
for (String path : paths) {
|
||||
pathMatchers.add(new AntPathRequestMatcher(server.getPath(path)));
|
||||
}
|
||||
delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE
|
||||
: new OrRequestMatcher(pathMatchers);
|
||||
}
|
||||
return delegate.matches(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String[] getEndpointPaths(EndpointHandlerMapping endpointHandlerMapping) {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.condition;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* {@link Conditional} that matches according to the way the management MVC endpoints are
|
||||
* deployed.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Conditional(OnManagementMvcCondition.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
public @interface ConditionalOnManagementMvcContext {
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.condition;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link Condition} that checks whether or not the management MVC endpoints are in the
|
||||
* main context.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class OnManagementMvcCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
RelaxedPropertyResolver management = new RelaxedPropertyResolver(
|
||||
context.getEnvironment(), "management.");
|
||||
RelaxedPropertyResolver server = new RelaxedPropertyResolver(
|
||||
context.getEnvironment(), "server.");
|
||||
Integer managementPort = management.getProperty("port", Integer.class);
|
||||
if (managementPort == null) {
|
||||
ManagementServerProperties managementServerProperties = getBeanCarefully(
|
||||
context, ManagementServerProperties.class);
|
||||
if (managementServerProperties != null) {
|
||||
managementPort = managementServerProperties.getPort();
|
||||
}
|
||||
}
|
||||
if (managementPort != null && managementPort < 0) {
|
||||
return new ConditionOutcome(false, "The mangagement port is disabled");
|
||||
}
|
||||
if (!(context.getResourceLoader() instanceof WebApplicationContext)) {
|
||||
// Current context is not a webapp
|
||||
return new ConditionOutcome(false, "The context is not a webapp");
|
||||
}
|
||||
Integer serverPort = server.getProperty("port", Integer.class);
|
||||
if (serverPort == null) {
|
||||
ServerProperties serverProperties = getBeanCarefully(context,
|
||||
ServerProperties.class);
|
||||
if (serverProperties != null) {
|
||||
serverPort = serverProperties.getPort();
|
||||
}
|
||||
}
|
||||
if ((managementPort == null)
|
||||
|| (serverPort == null && managementPort.equals(8080))
|
||||
|| (managementPort != 0 && managementPort.equals(serverPort))) {
|
||||
return new ConditionOutcome(true,
|
||||
"The main context is the management context");
|
||||
}
|
||||
return new ConditionOutcome(false,
|
||||
"The main context is not the management context");
|
||||
}
|
||||
|
||||
private <T> T getBeanCarefully(ConditionContext context, Class<T> type) {
|
||||
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
|
||||
context.getBeanFactory(), type, false, false);
|
||||
if (names.length == 1) {
|
||||
BeanDefinition original = findBeanDefinition(context.getBeanFactory(), names[0]);
|
||||
if (original instanceof RootBeanDefinition) {
|
||||
DefaultListableBeanFactory temp = new DefaultListableBeanFactory();
|
||||
temp.setParentBeanFactory(context.getBeanFactory());
|
||||
temp.registerBeanDefinition("bean",
|
||||
((RootBeanDefinition) original).cloneBeanDefinition());
|
||||
return temp.getBean(type);
|
||||
}
|
||||
return BeanFactoryUtils.beanOfType(context.getBeanFactory(), type, false,
|
||||
false);
|
||||
}
|
||||
;
|
||||
return null;
|
||||
}
|
||||
|
||||
private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {
|
||||
BeanDefinition original = null;
|
||||
while (beanFactory!=null && original==null){
|
||||
if (beanFactory.containsLocalBean(name)) {
|
||||
original = beanFactory.getBeanDefinition(name);
|
||||
} else {
|
||||
BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
|
||||
if (parentBeanFactory instanceof ConfigurableListableBeanFactory) {
|
||||
beanFactory = (ConfigurableListableBeanFactory) parentBeanFactory;
|
||||
} else {
|
||||
beanFactory = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import java.util.Set;
|
|||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
|
|
@ -52,8 +52,8 @@ public class MvcEndpoints implements ApplicationContextAware, InitializingBean {
|
|||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Collection<MvcEndpoint> existing = this.applicationContext.getBeansOfType(
|
||||
MvcEndpoint.class).values();
|
||||
Collection<MvcEndpoint> existing = BeanFactoryUtils.beansOfTypeIncludingAncestors(
|
||||
this.applicationContext, MvcEndpoint.class).values();
|
||||
this.endpoints.addAll(existing);
|
||||
this.customTypes = findEndpointClasses(existing);
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
|
|
@ -17,3 +17,6 @@ org.springframework.boot.actuate.autoconfigure.MetricExportAutoConfiguration,\
|
|||
org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
|
||||
|
||||
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration=\
|
||||
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration
|
|
@ -16,6 +16,18 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
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;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
|
@ -30,6 +42,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration.ManagementContextResolver;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
|
||||
|
@ -71,18 +84,6 @@ import org.springframework.web.servlet.ModelAndView;
|
|||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
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;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link EndpointWebMvcAutoConfiguration}.
|
||||
*
|
||||
|
@ -146,9 +147,10 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
assertContent("/endpoint", ports.get().server, null);
|
||||
assertContent("/controller", ports.get().management, null);
|
||||
assertContent("/endpoint", ports.get().management, "endpointoutput");
|
||||
ApplicationContext managementContext = this.applicationContext.getBean(
|
||||
ManagementContextResolver.class).getApplicationContext();
|
||||
List<?> interceptors = (List<?>) ReflectionTestUtils.getField(
|
||||
this.applicationContext.getBean(EndpointHandlerMapping.class),
|
||||
"interceptors");
|
||||
managementContext.getBean(EndpointHandlerMapping.class), "interceptors");
|
||||
assertEquals(1, interceptors.size());
|
||||
this.applicationContext.close();
|
||||
assertAllClosed();
|
||||
|
@ -243,7 +245,7 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
|
||||
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
|
||||
new ServerPortInfoApplicationContextInitializer()
|
||||
.initialize(this.applicationContext);
|
||||
.initialize(this.applicationContext);
|
||||
this.applicationContext.refresh();
|
||||
Integer localServerPort = this.applicationContext.getEnvironment().getProperty(
|
||||
"local.server.port", Integer.class);
|
||||
|
@ -259,7 +261,7 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
@Test
|
||||
public void portPropertiesOnDifferentPort() throws Exception {
|
||||
new ServerPortInfoApplicationContextInitializer()
|
||||
.initialize(this.applicationContext);
|
||||
.initialize(this.applicationContext);
|
||||
this.applicationContext.register(RootConfig.class, DifferentPortConfig.class,
|
||||
BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class,
|
||||
ErrorMvcAutoConfiguration.class);
|
||||
|
@ -420,12 +422,12 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
|
||||
@Configuration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class,
|
||||
ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
|
||||
EmbeddedServletContainerAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class,
|
||||
ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
|
||||
protected static class BaseConfiguration {
|
||||
|
||||
}
|
||||
|
@ -583,7 +585,7 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
}
|
||||
|
||||
private static class GrabManagementPort implements
|
||||
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
|
||||
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
|
||||
|
||||
private ApplicationContext rootContext;
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.mvc;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalToIgnoringCase;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
@ -37,12 +43,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalToIgnoringCase;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link EnvironmentMvcEndpoint}
|
||||
*
|
||||
|
@ -103,11 +103,6 @@ public class EnvironmentMvcEndpointTests {
|
|||
return new EnvironmentEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EnvironmentMvcEndpoint mvcEndpoint() {
|
||||
return new EnvironmentMvcEndpoint(endpoint());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.mvc;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -41,12 +47,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetricsMvcEndpoint}
|
||||
*
|
||||
|
@ -150,11 +150,6 @@ public class MetricsMvcEndpointTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MetricsMvcEndpoint mvcEndpoint() {
|
||||
return new MetricsMvcEndpoint(endpoint());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,17 +16,51 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Properties used to configure resource handling.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @author Dave Syer
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
|
||||
public class ResourceProperties {
|
||||
public class ResourceProperties implements ResourceLoaderAware {
|
||||
|
||||
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
|
||||
|
||||
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
|
||||
"classpath:/META-INF/resources/", "classpath:/resources/",
|
||||
"classpath:/static/", "classpath:/public/" };
|
||||
|
||||
private static final String[] RESOURCE_LOCATIONS;
|
||||
static {
|
||||
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
|
||||
+ SERVLET_RESOURCE_LOCATIONS.length];
|
||||
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
|
||||
SERVLET_RESOURCE_LOCATIONS.length);
|
||||
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
|
||||
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
|
||||
}
|
||||
|
||||
private static final String[] STATIC_INDEX_HTML_RESOURCES;
|
||||
static {
|
||||
STATIC_INDEX_HTML_RESOURCES = new String[RESOURCE_LOCATIONS.length];
|
||||
for (int i = 0; i < STATIC_INDEX_HTML_RESOURCES.length; i++) {
|
||||
STATIC_INDEX_HTML_RESOURCES[i] = RESOURCE_LOCATIONS[i] + "index.html";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache period for the resources served by the resource handler, in seconds.
|
||||
|
@ -40,6 +74,19 @@ public class ResourceProperties {
|
|||
|
||||
private final Chain chain = new Chain();
|
||||
|
||||
/**
|
||||
* Locations of static resources. Defaults to classpath:[/META-INF/resources/, /resources/, /static/, /public/]
|
||||
* plus context:/ (the root of the servlet context).
|
||||
*/
|
||||
private String[] staticLocations = RESOURCE_LOCATIONS;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
public Integer getCachePeriod() {
|
||||
return this.cachePeriod;
|
||||
}
|
||||
|
@ -211,4 +258,52 @@ public class ResourceProperties {
|
|||
|
||||
}
|
||||
|
||||
public String[] getStaticLocations() {
|
||||
return this.staticLocations;
|
||||
}
|
||||
|
||||
public void setStaticLocations(String[] staticLocations) {
|
||||
this.staticLocations = staticLocations;
|
||||
}
|
||||
|
||||
private String[] getStaticWelcomePageLocations() {
|
||||
String[] result = new String[staticLocations.length];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
String location = staticLocations[i];
|
||||
if (!location.endsWith("/")) {
|
||||
location = location + "/";
|
||||
}
|
||||
result[i] = location + "index.html";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Resource> getFaviconLocations() {
|
||||
List<Resource> locations = new ArrayList<Resource>(
|
||||
CLASSPATH_RESOURCE_LOCATIONS.length + 1);
|
||||
if (resourceLoader != null) {
|
||||
for (String location : CLASSPATH_RESOURCE_LOCATIONS) {
|
||||
locations.add(this.resourceLoader.getResource(location));
|
||||
}
|
||||
}
|
||||
locations.add(new ClassPathResource("/"));
|
||||
return Collections.unmodifiableList(locations);
|
||||
}
|
||||
|
||||
public Resource getWelcomePage() {
|
||||
for (String location : getStaticWelcomePageLocations()) {
|
||||
Resource resource = this.resourceLoader.getResource(location);
|
||||
if (resource.exists()) {
|
||||
try {
|
||||
resource.getURL();
|
||||
return resource;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -41,7 +39,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
|
|||
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
@ -49,7 +46,6 @@ import org.springframework.context.annotation.Primary;
|
|||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.format.Formatter;
|
||||
|
@ -104,30 +100,6 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
|||
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
|
||||
public class WebMvcAutoConfiguration {
|
||||
|
||||
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
|
||||
|
||||
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
|
||||
"classpath:/META-INF/resources/", "classpath:/resources/",
|
||||
"classpath:/static/", "classpath:/public/" };
|
||||
|
||||
private static final String[] RESOURCE_LOCATIONS;
|
||||
static {
|
||||
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
|
||||
+ SERVLET_RESOURCE_LOCATIONS.length];
|
||||
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
|
||||
SERVLET_RESOURCE_LOCATIONS.length);
|
||||
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
|
||||
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
|
||||
}
|
||||
|
||||
private static final String[] STATIC_INDEX_HTML_RESOURCES;
|
||||
static {
|
||||
STATIC_INDEX_HTML_RESOURCES = new String[RESOURCE_LOCATIONS.length];
|
||||
for (int i = 0; i < STATIC_INDEX_HTML_RESOURCES.length; i++) {
|
||||
STATIC_INDEX_HTML_RESOURCES[i] = RESOURCE_LOCATIONS[i] + "index.html";
|
||||
}
|
||||
}
|
||||
|
||||
public static String DEFAULT_PREFIX = "";
|
||||
|
||||
public static String DEFAULT_SUFFIX = "";
|
||||
|
@ -267,7 +239,7 @@ public class WebMvcAutoConfiguration {
|
|||
}
|
||||
if (!registry.hasMappingForPattern("/**")) {
|
||||
registerResourceChain(registry.addResourceHandler("/**")
|
||||
.addResourceLocations(RESOURCE_LOCATIONS)
|
||||
.addResourceLocations(resourceProperties.getStaticLocations())
|
||||
.setCachePeriod(cachePeriod));
|
||||
}
|
||||
}
|
||||
|
@ -310,31 +282,19 @@ public class WebMvcAutoConfiguration {
|
|||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
addStaticIndexHtmlViewControllers(registry);
|
||||
}
|
||||
|
||||
private void addStaticIndexHtmlViewControllers(ViewControllerRegistry registry) {
|
||||
for (String resource : STATIC_INDEX_HTML_RESOURCES) {
|
||||
if (this.resourceLoader.getResource(resource).exists()) {
|
||||
try {
|
||||
logger.info("Adding welcome page: "
|
||||
+ this.resourceLoader.getResource(resource).getURL());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
// Use forward: prefix so that no view resolution is done
|
||||
registry.addViewController("/").setViewName("forward:index.html");
|
||||
return;
|
||||
}
|
||||
Resource page = resourceProperties.getWelcomePage();
|
||||
if (page != null) {
|
||||
logger.info("Adding welcome page: " + page);
|
||||
registry.addViewController("/").setViewName("forward:index.html");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
|
||||
public static class FaviconConfiguration implements ResourceLoaderAware {
|
||||
public static class FaviconConfiguration {
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
@Autowired
|
||||
private ResourceProperties resourceProperties = new ResourceProperties();
|
||||
|
||||
@Bean
|
||||
public SimpleUrlHandlerMapping faviconHandlerMapping() {
|
||||
|
@ -345,28 +305,13 @@ public class WebMvcAutoConfiguration {
|
|||
return mapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceHttpRequestHandler faviconRequestHandler() {
|
||||
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
|
||||
requestHandler.setLocations(getLocations());
|
||||
requestHandler.setLocations(resourceProperties.getFaviconLocations());
|
||||
return requestHandler;
|
||||
}
|
||||
|
||||
private List<Resource> getLocations() {
|
||||
List<Resource> locations = new ArrayList<Resource>(
|
||||
CLASSPATH_RESOURCE_LOCATIONS.length + 1);
|
||||
for (String location : CLASSPATH_RESOURCE_LOCATIONS) {
|
||||
locations.add(this.resourceLoader.getResource(location));
|
||||
}
|
||||
locations.add(new ClassPathResource("/"));
|
||||
return Collections.unmodifiableList(locations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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.autoconfigure.web;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Tests for welcome page using {@link MockMvc} and {@link SpringJUnit4ClassRunner}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class WelcomePageMockMvcTests {
|
||||
|
||||
private ConfigurableWebApplicationContext wac;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (wac != null) {
|
||||
wac.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homePageNotFound() throws Exception {
|
||||
wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder(
|
||||
TestConfiguration.class).run();
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
this.mockMvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homePageCustomLocation() throws Exception {
|
||||
wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder(
|
||||
TestConfiguration.class).properties(
|
||||
"spring.resources.staticLocations:classpath:/custom/").run();
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
this.mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homePageCustomLocationNoTrailingSlash() throws Exception {
|
||||
wac = (ConfigurableWebApplicationContext) new SpringApplicationBuilder(
|
||||
TestConfiguration.class).properties("spring.resources.staticLocations:classpath:/custom").run();
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||
this.mockMvc.perform(get("/")).andExpect(status().isOk()).andReturn();
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({ EmbeddedServletContainerAutoConfiguration.EmbeddedTomcat.class,
|
||||
EmbeddedServletContainerAutoConfiguration.class,
|
||||
ServerPropertiesAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
|
||||
protected static @interface MinimalWebConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@MinimalWebConfiguration
|
||||
public static class TestConfiguration {
|
||||
|
||||
// For manual testing
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestConfiguration.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<html><body>Hello!</body></html>
|
|
@ -150,6 +150,23 @@ For example, the following will disable _all_ endpoints except for `info`:
|
|||
endpoints.info.enabled=true
|
||||
----
|
||||
|
||||
[[production-ready-customizing-endpoints-programmatically]]
|
||||
=== Adding Custom Endpoints
|
||||
|
||||
If you add a `@Bean` of type `Endpoint` then it will automatically be
|
||||
exposed over JMX and HTTP (if there is an server available). An HTTP
|
||||
endpoints can be customized further by creating a bean of type
|
||||
`MvcEndpoint`. Your `MvcEndpoint` is not a `@Controller` but it can use
|
||||
`@RequestMapping` (and `@Managed*`) to expose resources.
|
||||
|
||||
TIP: If you are doing this as a library feature consider adding a
|
||||
configuration class to `/META-INF/spring.factories` under the key
|
||||
`org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration`.
|
||||
If you do that then the endpoint will move to a child context with all
|
||||
the other MVC endpoints if your users ask for a separate management port
|
||||
or address. A configuration declared this way can be a `WebConfigurerAdapter`
|
||||
if it wants to add static resources (for instance) to the management
|
||||
endpoints.
|
||||
|
||||
|
||||
[[production-ready-health]]
|
||||
|
@ -1284,7 +1301,8 @@ described below.
|
|||
|
||||
[[production-ready-process-monitoring-configuration]]
|
||||
=== Extend configuration
|
||||
In `META-INF/spring.factories` file you have to activate the listener(s):
|
||||
In `META-INF/spring.factories` file you can activate the listener(s) that
|
||||
writes a PID file. Example:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
|
|
|
@ -1175,6 +1175,13 @@ Spring decides not to handle it. Most of the time this will not happen (unless y
|
|||
the default MVC configuration) because Spring will always be able to handle requests
|
||||
through the `DispatcherServlet`.
|
||||
|
||||
You can customize the static resource locations using
|
||||
`spring.resources.staticLocations` (replacing the default values with
|
||||
a list of directory locations). If you do this the default welcome
|
||||
page detection will switch to your custom locations, so if there is an
|
||||
`index.html` in any of your locations on startup, it will be the home
|
||||
page of the application.
|
||||
|
||||
In addition to the '`standard`' static resource locations above, a special case is made
|
||||
for http://www.webjars.org/[Webjars content]. Any resources with a path in `+/webjars/**+`
|
||||
will be served from jar files if they are packaged in the Webjars format.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# logging.file: /tmp/logs/app.log
|
||||
# logging.level.org.springframework.security: INFO
|
||||
# logging.level.org.springframework.security: DEBUG
|
||||
management.address: 127.0.0.1
|
||||
#management.port: 8181
|
||||
endpoints.shutdown.enabled: true
|
||||
|
|
Loading…
Reference in New Issue