Merge pull request #16449 from chang-chao

* pr/16449:
  Polish "Make Actuator dedicated ConversionService configurable"
  Make Actuator dedicated ConversionService configurable

Closes gh-16449
pull/17611/head
Stephane Nicoll 5 years ago
commit 42075ddf28

@ -16,14 +16,23 @@
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointConverter;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor; import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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; import org.springframework.core.env.Environment;
/** /**
@ -32,6 +41,7 @@ import org.springframework.core.env.Environment;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Chao Chang
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ -39,8 +49,24 @@ public class EndpointAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public ParameterValueMapper endpointOperationParameterMapper() { public ParameterValueMapper endpointOperationParameterMapper(
return new ConversionServiceParameterValueMapper(); @EndpointConverter ObjectProvider<Converter<?, ?>> converters,
@EndpointConverter ObjectProvider<GenericConverter> genericConverters) {
ConversionService conversionService = createConversionService(
converters.orderedStream().collect(Collectors.toList()),
genericConverters.orderedStream().collect(Collectors.toList()));
return new ConversionServiceParameterValueMapper(conversionService);
}
private ConversionService createConversionService(List<Converter<?, ?>> converters,
List<GenericConverter> genericConverters) {
if (genericConverters.isEmpty() && converters.isEmpty()) {
return ApplicationConversionService.getSharedInstance();
}
ApplicationConversionService conversionService = new ApplicationConversionService();
converters.forEach(conversionService::addConverter);
genericConverters.forEach(conversionService::addConverter);
return conversionService;
} }
@Bean @Bean

@ -0,0 +1,205 @@
/*
* Copyright 2012-2019 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.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.annotation.EndpointConverter;
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.ApplicationContextRunner;
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
*/
class EndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(EndpointAutoConfiguration.class));
@Test
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
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
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
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
Converter<String, Person> personConverter() {
return new PersonConverter();
}
}
@Configuration(proxyBeanMethods = false)
static class NonQualifiedConverterConfiguration {
@Bean
Converter<String, Person> personConverter() {
return new PersonConverter();
}
}
@Configuration(proxyBeanMethods = false)
static class GenericConverterConfiguration {
@Bean
@EndpointConverter
GenericConverter genericPersonConverter() {
return new GenericPersonConverter();
}
}
@Configuration(proxyBeanMethods = false)
static class NonQualifiedGenericConverterConfiguration {
@Bean
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;
}
}
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;
}
}
}

@ -0,0 +1,39 @@
/*
* Copyright 2012-2019 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.endpoint.annotation;
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;
/**
* Qualifier for beans that are needed to convert {@link Endpoint} input parameters.
*
* @author Chao Chang
* @since 2.2.0
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
public @interface EndpointConverter {
}

@ -558,8 +558,8 @@ automatically if you are using Spring Boot's Gradle plugin or if you are using M
The parameters passed to endpoint operation methods are, if necessary, automatically The parameters passed to endpoint operation methods are, if necessary, automatically
converted to the required type. Before calling an operation method, the input received via converted to the required type. Before calling an operation method, the input received via
JMX or an HTTP request is converted to the required types using an instance of JMX or an HTTP request is converted to the required types using an instance of
`ApplicationConversionService`. `ApplicationConversionService` as well as any `Converter` or `GenericConverter` beans
qualified with `@EndpointConverter`.
[[production-ready-endpoints-custom-web]] [[production-ready-endpoints-custom-web]]

Loading…
Cancel
Save