Allow user to provide own Gson or ObjectMapper
This commit is contained in:
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.cloud.function.context.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
|
||||
import org.springframework.cloud.function.context.FunctionRegistration;
|
||||
import org.springframework.cloud.function.context.FunctionRegistry;
|
||||
import org.springframework.cloud.function.context.catalog.InMemoryFunctionCatalog;
|
||||
import org.springframework.cloud.function.json.JsonMapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ContextFunctionCatalogBeanRegistrar implements ApplicationContextAware,
|
||||
ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
private GenericApplicationContext context;
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext applicationContext) {
|
||||
if (applicationContext.getEnvironment().getProperty("spring.functional.enabled",
|
||||
Boolean.class, false)) {
|
||||
register(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void register(BeanDefinitionRegistry registry) throws BeansException {
|
||||
try {
|
||||
register(registry, beanFactory(registry));
|
||||
}
|
||||
catch (BeansException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BeanCreationException("Cannot register from " + getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
Assert.isInstanceOf(GenericApplicationContext.class, context,
|
||||
"Expecting a GenericApplicationContext");
|
||||
this.context = (GenericApplicationContext) context;
|
||||
}
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory(BeanDefinitionRegistry registry) {
|
||||
if (this.context == null && registry instanceof AutowireCapableBeanFactory) {
|
||||
((AutowireCapableBeanFactory) registry).initializeBean(this,
|
||||
getClass().getSimpleName());
|
||||
}
|
||||
if (this.context == null && registry instanceof GenericApplicationContext) {
|
||||
this.context = (GenericApplicationContext) registry;
|
||||
}
|
||||
if (this.context != null) {
|
||||
return this.context.getDefaultListableBeanFactory();
|
||||
}
|
||||
if (registry instanceof ConfigurableListableBeanFactory) {
|
||||
return (ConfigurableListableBeanFactory) registry;
|
||||
}
|
||||
if (registry instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) registry).getBeanFactory();
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Cannot locate ConfigurableListableBeanFactory in " + registry);
|
||||
}
|
||||
|
||||
protected void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory factory) throws Exception {
|
||||
|
||||
performPreinitialization();
|
||||
|
||||
context.registerBean(PropertySourcesPlaceholderConfigurer.class,
|
||||
() -> PropertyPlaceholderAutoConfiguration
|
||||
.propertySourcesPlaceholderConfigurer());
|
||||
|
||||
context.registerBean(
|
||||
AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
|
||||
AutowiredAnnotationBeanPostProcessor.class);
|
||||
context.registerBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
|
||||
ConfigurationBeanFactoryMetadata.class,
|
||||
() -> new ConfigurationBeanFactoryMetadata());
|
||||
context.registerBean(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME,
|
||||
ConfigurationPropertiesBindingPostProcessor.class,
|
||||
() -> new ConfigurationPropertiesBindingPostProcessor());
|
||||
|
||||
// TODO: use Boot to create Gson and ObjectMapper
|
||||
if (ClassUtils.isPresent("com.google.gson.Gson", null)
|
||||
&& !"gson".equals(context.getEnvironment().getProperty(
|
||||
ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
|
||||
"gson"))) {
|
||||
context.registerBean(Gson.class, () -> new Gson());
|
||||
context.registerBean(JsonMapper.class,
|
||||
() -> new ContextFunctionCatalogAutoConfiguration.GsonConfiguration()
|
||||
.jsonMapper(context.getBean(Gson.class)));
|
||||
}
|
||||
else if (ClassUtils.isPresent(
|
||||
"com.fasterxml.jackson.databind.ObjectMapper.ObjectMapper", null)) {
|
||||
context.registerBean(ObjectMapper.class, () -> new ObjectMapper());
|
||||
context.registerBean(JsonMapper.class,
|
||||
() -> new ContextFunctionCatalogAutoConfiguration.JacksonConfiguration()
|
||||
.jsonMapper(context.getBean(ObjectMapper.class)));
|
||||
|
||||
}
|
||||
|
||||
context.registerBean(InMemoryFunctionCatalog.class,
|
||||
() -> new InMemoryFunctionCatalog());
|
||||
context.registerBean(FunctionRegistrationPostProcessor.class,
|
||||
() -> new FunctionRegistrationPostProcessor(
|
||||
context.getBean(FunctionRegistry.class)));
|
||||
}
|
||||
|
||||
private void performPreinitialization() {
|
||||
try {
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runSafely(() -> new DefaultFormattingConversionService());
|
||||
}
|
||||
|
||||
public void runSafely(Runnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}, "background-preinit");
|
||||
thread.start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
|
||||
private class FunctionRegistrationPostProcessor implements BeanPostProcessor {
|
||||
private final FunctionRegistry catalog;
|
||||
|
||||
public FunctionRegistrationPostProcessor(FunctionRegistry catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof FunctionRegistration) {
|
||||
FunctionRegistration<?> registration = (FunctionRegistration<?>) bean;
|
||||
if (registration.getNames().isEmpty()) {
|
||||
registration = registration.name(beanName);
|
||||
}
|
||||
if (registration.getType() == null) {
|
||||
throw new IllegalStateException(
|
||||
"You need an explicit type for the function: " + beanName);
|
||||
// TODO: in principle Spring could know how to extract this from the
|
||||
// supplier, but in practice there is no functional bean registration
|
||||
// with parametric types.
|
||||
}
|
||||
catalog.register(registration);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2016-2017 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.cloud.function.context.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
|
||||
import org.springframework.cloud.function.context.FunctionRegistration;
|
||||
import org.springframework.cloud.function.context.FunctionRegistry;
|
||||
import org.springframework.cloud.function.context.catalog.InMemoryFunctionCatalog;
|
||||
import org.springframework.cloud.function.json.JsonMapper;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ContextFunctionCatalogInitializer
|
||||
implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext applicationContext) {
|
||||
if (applicationContext.getEnvironment().getProperty("spring.functional.enabled",
|
||||
Boolean.class, false)) {
|
||||
ContextFunctionCatalogBeanRegistrar registrar = new ContextFunctionCatalogBeanRegistrar(
|
||||
applicationContext);
|
||||
applicationContext.addBeanFactoryPostProcessor(registrar);
|
||||
}
|
||||
}
|
||||
|
||||
static class ContextFunctionCatalogBeanRegistrar
|
||||
implements BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
private GenericApplicationContext context;
|
||||
|
||||
public ContextFunctionCatalogBeanRegistrar(
|
||||
GenericApplicationContext applicationContext) {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
throws BeansException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
|
||||
throws BeansException {
|
||||
try {
|
||||
register(registry, this.context.getDefaultListableBeanFactory());
|
||||
}
|
||||
catch (BeansException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BeanCreationException("Cannot register from " + getClass(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory factory) throws Exception {
|
||||
|
||||
performPreinitialization();
|
||||
|
||||
context.registerBean(PropertySourcesPlaceholderConfigurer.class,
|
||||
() -> PropertyPlaceholderAutoConfiguration
|
||||
.propertySourcesPlaceholderConfigurer());
|
||||
|
||||
context.registerBean(
|
||||
AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
|
||||
AutowiredAnnotationBeanPostProcessor.class);
|
||||
context.registerBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
|
||||
ConfigurationBeanFactoryMetadata.class,
|
||||
() -> new ConfigurationBeanFactoryMetadata());
|
||||
context.registerBean(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME,
|
||||
ConfigurationPropertiesBindingPostProcessor.class,
|
||||
() -> new ConfigurationPropertiesBindingPostProcessor());
|
||||
|
||||
if (ClassUtils.isPresent("com.google.gson.Gson", null)
|
||||
&& !"gson".equals(context.getEnvironment().getProperty(
|
||||
ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
|
||||
"gson"))) {
|
||||
if (context.getBeanNamesForType(Gson.class, false, false).length == 0) {
|
||||
context.registerBean(Gson.class, () -> new Gson());
|
||||
}
|
||||
context.registerBean(JsonMapper.class,
|
||||
() -> new ContextFunctionCatalogAutoConfiguration.GsonConfiguration()
|
||||
.jsonMapper(context.getBean(Gson.class)));
|
||||
}
|
||||
else if (ClassUtils.isPresent(
|
||||
"com.fasterxml.jackson.databind.ObjectMapper.ObjectMapper", null)) {
|
||||
if (context.getBeanNamesForType(ObjectMapper.class, false,
|
||||
false).length == 0) {
|
||||
context.registerBean(ObjectMapper.class, () -> new ObjectMapper());
|
||||
}
|
||||
context.registerBean(JsonMapper.class,
|
||||
() -> new ContextFunctionCatalogAutoConfiguration.JacksonConfiguration()
|
||||
.jsonMapper(context.getBean(ObjectMapper.class)));
|
||||
|
||||
}
|
||||
|
||||
context.registerBean(InMemoryFunctionCatalog.class,
|
||||
() -> new InMemoryFunctionCatalog());
|
||||
context.registerBean(FunctionRegistrationPostProcessor.class,
|
||||
() -> new FunctionRegistrationPostProcessor(
|
||||
context.getBean(FunctionRegistry.class)));
|
||||
}
|
||||
|
||||
private void performPreinitialization() {
|
||||
try {
|
||||
Thread thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runSafely(() -> new DefaultFormattingConversionService());
|
||||
}
|
||||
|
||||
public void runSafely(Runnable runnable) {
|
||||
try {
|
||||
runnable.run();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}, "background-preinit");
|
||||
thread.start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
|
||||
private class FunctionRegistrationPostProcessor implements BeanPostProcessor {
|
||||
private final FunctionRegistry catalog;
|
||||
|
||||
public FunctionRegistrationPostProcessor(FunctionRegistry catalog) {
|
||||
this.catalog = catalog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof FunctionRegistration) {
|
||||
FunctionRegistration<?> registration = (FunctionRegistration<?>) bean;
|
||||
if (registration.getNames().isEmpty()) {
|
||||
registration = registration.name(beanName);
|
||||
}
|
||||
if (registration.getType() == null) {
|
||||
throw new IllegalStateException(
|
||||
"You need an explicit type for the function: "
|
||||
+ beanName);
|
||||
// TODO: in principle Spring could know how to extract this from
|
||||
// the supplier, but in practice there is no functional bean
|
||||
// registration with parametric types.
|
||||
}
|
||||
catalog.register(registration);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,4 @@ org.springframework.cloud.function.context.WrapperDetector=\
|
||||
org.springframework.cloud.function.context.config.FluxWrapperDetector
|
||||
|
||||
org.springframework.context.ApplicationContextInitializer=\
|
||||
org.springframework.cloud.function.context.config.ContextFunctionCatalogBeanRegistrar
|
||||
org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer
|
||||
@@ -23,6 +23,8 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -45,7 +47,7 @@ import reactor.core.publisher.Mono;
|
||||
* @author Dave Syert
|
||||
*
|
||||
*/
|
||||
public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
public class ContextFunctionCatalogInitializerTests {
|
||||
|
||||
private GenericApplicationContext context;
|
||||
private FunctionCatalog catalog;
|
||||
@@ -67,7 +69,7 @@ public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
// TODO: support for function composition
|
||||
}
|
||||
|
||||
@Test(expected=BeanCreationException.class)
|
||||
@Test(expected = BeanCreationException.class)
|
||||
public void missingType() {
|
||||
create(MissingTypeConfiguration.class);
|
||||
assertThat(context.getBean("function")).isInstanceOf(FunctionRegistration.class);
|
||||
@@ -110,8 +112,8 @@ public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
assertThat(function.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO");
|
||||
assertThat(bean).isNotSameAs(function);
|
||||
assertThat(inspector.getRegistration(function)).isNotNull();
|
||||
assertThat(inspector.getRegistration(function).getType())
|
||||
.isEqualTo(FunctionType.from(String.class).to(String.class).wrap(Flux.class));
|
||||
assertThat(inspector.getRegistration(function).getType()).isEqualTo(
|
||||
FunctionType.from(String.class).to(String.class).wrap(Flux.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -132,6 +134,14 @@ public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
assertThat(context.getBean(SimpleConfiguration.class).list).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideGson() {
|
||||
create(GsonConfiguration.class);
|
||||
Gson user = context.getBean(GsonConfiguration.class).gson();
|
||||
Gson bean = context.getBean(Gson.class);
|
||||
assertThat(user).isSameAs(bean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void create(
|
||||
Class<? extends ApplicationContextInitializer<GenericApplicationContext>> type,
|
||||
@@ -146,7 +156,8 @@ public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
for (ApplicationContextInitializer<GenericApplicationContext> type : types) {
|
||||
type.initialize(context);
|
||||
}
|
||||
new ContextFunctionCatalogBeanRegistrar().register(context);
|
||||
new ContextFunctionCatalogInitializer.ContextFunctionCatalogBeanRegistrar(context)
|
||||
.postProcessBeanDefinitionRegistry(context);
|
||||
context.refresh();
|
||||
catalog = context.getBean(FunctionCatalog.class);
|
||||
inspector = context.getBean(FunctionInspector.class);
|
||||
@@ -212,6 +223,24 @@ public class ContextFunctionCatalogBeanRegistrarTests {
|
||||
}
|
||||
}
|
||||
|
||||
protected static class GsonConfiguration
|
||||
implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public void initialize(GenericApplicationContext context) {
|
||||
context.registerBean("gson", Gson.class, this::gson);
|
||||
context.registerBean(GsonConfiguration.class, () -> this);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Gson gson() {
|
||||
return this.gson;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class DependencyInjectionConfiguration
|
||||
implements ApplicationContextInitializer<GenericApplicationContext> {
|
||||
|
||||
Reference in New Issue
Block a user