diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrar.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrar.java deleted file mode 100644 index 3f2638deb..000000000 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrar.java +++ /dev/null @@ -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 { - - 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; - } - - } -} diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializer.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializer.java new file mode 100644 index 000000000..7872d6193 --- /dev/null +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializer.java @@ -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 { + + @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; + } + + } + + } +} diff --git a/spring-cloud-function-context/src/main/resources/META-INF/spring.factories b/spring-cloud-function-context/src/main/resources/META-INF/spring.factories index fea519140..aa9a9c745 100644 --- a/spring-cloud-function-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-function-context/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file +org.springframework.cloud.function.context.config.ContextFunctionCatalogInitializer \ No newline at end of file diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrarTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java similarity index 89% rename from spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrarTests.java rename to spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java index 0a0f9b786..67f15bbd5 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogBeanRegistrarTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogInitializerTests.java @@ -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> type, @@ -146,7 +156,8 @@ public class ContextFunctionCatalogBeanRegistrarTests { for (ApplicationContextInitializer 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 { + + 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 {