Make Actuator dedicated ConversionService configurable
See gh-16449
This commit is contained in:
parent
38a42a8600
commit
2aea437536
|
|
@ -16,14 +16,26 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.endpoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +44,7 @@ import org.springframework.core.env.Environment;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Chao Chang
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
|
@ -39,8 +52,10 @@ public class EndpointAutoConfiguration {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ParameterValueMapper endpointOperationParameterMapper() {
|
||||
return new ConversionServiceParameterValueMapper();
|
||||
public ParameterValueMapper endpointOperationParameterMapper(
|
||||
ApplicationContext applicationContext) {
|
||||
return new ConversionServiceParameterValueMapper(
|
||||
new Factory(applicationContext.getAutowireCapableBeanFactory()).create());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -49,4 +64,48 @@ public class EndpointAutoConfiguration {
|
|||
return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment));
|
||||
}
|
||||
|
||||
private static class Factory {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final List<Converter> converters;
|
||||
|
||||
private final List<GenericConverter> genericConverters;
|
||||
|
||||
Factory(BeanFactory beanFactory) {
|
||||
this.converters = beans(beanFactory, Converter.class,
|
||||
EndpointConverter.VALUE);
|
||||
this.genericConverters = beans(beanFactory, GenericConverter.class,
|
||||
EndpointConverter.VALUE);
|
||||
}
|
||||
|
||||
private <T> List<T> beans(BeanFactory beanFactory, Class<T> type,
|
||||
String qualifier) {
|
||||
if (beanFactory instanceof ListableBeanFactory) {
|
||||
return beans(type, qualifier, (ListableBeanFactory) beanFactory);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private <T> List<T> beans(Class<T> type, String qualifier,
|
||||
ListableBeanFactory beanFactory) {
|
||||
return new ArrayList<>(BeanFactoryAnnotationUtils
|
||||
.qualifiedBeansOfType(beanFactory, type, qualifier).values());
|
||||
}
|
||||
|
||||
public ConversionService create() {
|
||||
if (this.converters.isEmpty() && this.genericConverters.isEmpty()) {
|
||||
return ApplicationConversionService.getSharedInstance();
|
||||
}
|
||||
ApplicationConversionService conversionService = new ApplicationConversionService();
|
||||
for (Converter<?, ?> converter : this.converters) {
|
||||
conversionService.addConverter(converter);
|
||||
}
|
||||
for (GenericConverter genericConverter : this.genericConverters) {
|
||||
conversionService.addConverter(genericConverter);
|
||||
}
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.endpoint;
|
||||
|
||||
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.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
|
||||
/**
|
||||
* Qualifier for beans that are needed to be converters for {@link Endpoint}.
|
||||
*
|
||||
* @author Chao Chang
|
||||
*/
|
||||
@Qualifier(EndpointConverter.VALUE)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface EndpointConverter {
|
||||
|
||||
/**
|
||||
* Concrete value for the {@link Qualifier @Qualifier}.
|
||||
*/
|
||||
String VALUE = "org.springframework.boot.actuate.autoconfigure.endpoint.EndpointConverter";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.endpoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link EndpointAutoConfiguration}.
|
||||
*
|
||||
* @author Chao Chang
|
||||
*/
|
||||
public class EndpointAutoConfigurationTests {
|
||||
|
||||
private static final AutoConfigurations CONFIGURATIONS = AutoConfigurations
|
||||
.of(EndpointAutoConfiguration.class);
|
||||
|
||||
private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(CONFIGURATIONS);
|
||||
|
||||
@Test
|
||||
public void mapShouldUseConfigurationConverter() {
|
||||
this.contextRunner.withUserConfiguration(ConverterConfiguration.class)
|
||||
.run((context) -> {
|
||||
ParameterValueMapper parameterValueMapper = context
|
||||
.getBean(ParameterValueMapper.class);
|
||||
Object paramValue = parameterValueMapper.mapParameterValue(
|
||||
new TestOperationParameter(Person.class), "John Smith");
|
||||
assertThat(paramValue).isInstanceOf(Person.class);
|
||||
Person person = (Person) paramValue;
|
||||
assertThat(person.firstName).isEqualTo("John");
|
||||
assertThat(person.lastName).isEqualTo("Smith");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapWhenConfigurationConverterIsNotQualifiedShouldNotConvert() {
|
||||
assertThatExceptionOfType(ParameterMappingException.class).isThrownBy(() -> {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NonQualifiedConverterConfiguration.class)
|
||||
.run((context) -> {
|
||||
ParameterValueMapper parameterValueMapper = context
|
||||
.getBean(ParameterValueMapper.class);
|
||||
parameterValueMapper.mapParameterValue(
|
||||
new TestOperationParameter(Person.class), "John Smith");
|
||||
});
|
||||
|
||||
}).withCauseInstanceOf(ConverterNotFoundException.class);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapShouldUseGenericConfigurationConverter() {
|
||||
this.contextRunner.withUserConfiguration(GenericConverterConfiguration.class)
|
||||
.run((context) -> {
|
||||
ParameterValueMapper parameterValueMapper = context
|
||||
.getBean(ParameterValueMapper.class);
|
||||
Object paramValue = parameterValueMapper.mapParameterValue(
|
||||
new TestOperationParameter(Person.class), "John Smith");
|
||||
assertThat(paramValue).isInstanceOf(Person.class);
|
||||
Person person = (Person) paramValue;
|
||||
assertThat(person.firstName).isEqualTo("John");
|
||||
assertThat(person.lastName).isEqualTo("Smith");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapWhenGenericConfigurationConverterIsNotQualifiedShouldNotConvert() {
|
||||
assertThatExceptionOfType(ParameterMappingException.class).isThrownBy(() -> {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(
|
||||
NonQualifiedGenericConverterConfiguration.class)
|
||||
.run((context) -> {
|
||||
ParameterValueMapper parameterValueMapper = context
|
||||
.getBean(ParameterValueMapper.class);
|
||||
parameterValueMapper.mapParameterValue(
|
||||
new TestOperationParameter(Person.class), "John Smith");
|
||||
});
|
||||
|
||||
}).withCauseInstanceOf(ConverterNotFoundException.class);
|
||||
|
||||
}
|
||||
|
||||
static class PersonConverter implements Converter<String, Person> {
|
||||
|
||||
@Override
|
||||
public Person convert(String source) {
|
||||
String[] content = StringUtils.split(source, " ");
|
||||
return new Person(content[0], content[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericPersonConverter implements GenericConverter {
|
||||
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return Collections.singleton(new ConvertiblePair(String.class, Person.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
String[] content = StringUtils.split((String) source, " ");
|
||||
return new Person(content[0], content[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@EndpointConverter
|
||||
public Converter<String, Person> personConverter() {
|
||||
return new PersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NonQualifiedConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
public Converter<String, Person> personConverter() {
|
||||
return new PersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class GenericConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@EndpointConverter
|
||||
public GenericConverter genericPersonConverter() {
|
||||
return new GenericPersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NonQualifiedGenericConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
public GenericConverter genericPersonConverter() {
|
||||
return new GenericPersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
private final String firstName;
|
||||
|
||||
private final String lastName;
|
||||
|
||||
Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return this.firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return this.lastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestOperationParameter implements OperationParameter {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
TestOperationParameter(Class<?> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMandatory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue