From b4f5c0339fe310ca54043c46d3ef1bc9167a1e62 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 10 Jul 2019 17:08:41 +0100 Subject: [PATCH] Add flag for disabling component scan --- .../main/asciidoc/spring-cloud-function.adoc | 11 ++ ...ntextFunctionCatalogAutoConfiguration.java | 77 ++++++------- .../ContextFunctionCatalogInitializer.java | 103 +++++++----------- 3 files changed, 86 insertions(+), 105 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-function.adoc b/docs/src/main/asciidoc/spring-cloud-function.adoc index d22cccd06..f8ff2528c 100644 --- a/docs/src/main/asciidoc/spring-cloud-function.adoc +++ b/docs/src/main/asciidoc/spring-cloud-function.adoc @@ -70,6 +70,17 @@ of a non publisher type (which is normal), it will be converted to a function that returns a publisher, so that it can be subscribed to in a controlled way. +=== Function Component Scan + +Spring Cloud Function will scan for implementations of `Function`, +`Consumer` and `Supplier` in a package called `functions` if it +exists. Using this feature you can write functions that have no +dependencies on Spring - not even the `@Component` annotation is +needed. If you want to use a different package, you can set +`spring.cloud.function.scan.packages`. You can also use +`spring.cloud.function.scan.enabled=false` to switch off the scan +completely. + === Function Routing Since version 2.2 Spring Cloud Function provides routing feature allowing diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java index 2e01b60c5..ec78891d5 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration.java @@ -78,11 +78,8 @@ import org.springframework.messaging.converter.StringMessageConverter; * @author Artem Bilan * @author Anshul Mehra */ -@Configuration +@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(FunctionCatalog.class) -@ComponentScan(basePackages = "${spring.cloud.function.scan.packages:functions}", // - includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { - Supplier.class, Function.class, Consumer.class })) public class ContextFunctionCatalogAutoConfiguration { static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper"; @@ -103,9 +100,18 @@ public class ContextFunctionCatalogAutoConfiguration { return new RoutingFunction(functionCatalog, functionInspector, messageConverter); } - protected static class BeanFactoryFunctionCatalog - extends AbstractComposableFunctionRegistry - implements SmartInitializingSingleton, BeanFactoryAware { + @Configuration(proxyBeanMethods = false) + @ComponentScan(basePackages = "${spring.cloud.function.scan.packages:functions}", // + includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, + classes = { Supplier.class, Function.class, Consumer.class })) + @ConditionalOnProperty(prefix = "spring.cloud.function.scan", name = "enabled", havingValue = "true", + matchIfMissing = true) + protected static class PlainFunctionScanConfiguration { + + } + + protected static class BeanFactoryFunctionCatalog extends AbstractComposableFunctionRegistry + implements SmartInitializingSingleton, BeanFactoryAware { private ApplicationEventPublisher applicationEventPublisher; @@ -118,16 +124,12 @@ public class ContextFunctionCatalogAutoConfiguration { @SuppressWarnings("rawtypes") @Override public void afterSingletonsInstantiated() { - Map supplierBeans = this.beanFactory - .getBeansOfType(Supplier.class); - Map functionBeans = this.beanFactory - .getBeansOfType(Function.class); - Map consumerBeans = this.beanFactory - .getBeansOfType(Consumer.class); + Map supplierBeans = this.beanFactory.getBeansOfType(Supplier.class); + Map functionBeans = this.beanFactory.getBeansOfType(Function.class); + Map consumerBeans = this.beanFactory.getBeansOfType(Consumer.class); Map functionRegistrationBeans = this.beanFactory - .getBeansOfType(FunctionRegistration.class); - this.doMerge(functionRegistrationBeans, consumerBeans, supplierBeans, - functionBeans); + .getBeansOfType(FunctionRegistration.class); + this.doMerge(functionRegistrationBeans, consumerBeans, supplierBeans, functionBeans); } @Override @@ -139,14 +141,12 @@ public class ContextFunctionCatalogAutoConfiguration { public void close() { if (this.applicationEventPublisher != null) { if (this.hasFunctions()) { - this.applicationEventPublisher - .publishEvent(new FunctionUnregistrationEvent(this, - Function.class, this.getFunctionNames())); + this.applicationEventPublisher.publishEvent( + new FunctionUnregistrationEvent(this, Function.class, this.getFunctionNames())); } if (this.hasSuppliers()) { - this.applicationEventPublisher - .publishEvent(new FunctionUnregistrationEvent(this, - Supplier.class, this.getSupplierNames())); + this.applicationEventPublisher.publishEvent( + new FunctionUnregistrationEvent(this, Supplier.class, this.getSupplierNames())); } } } @@ -155,8 +155,7 @@ public class ContextFunctionCatalogAutoConfiguration { protected FunctionType findType(FunctionRegistration functionRegistration, String name) { FunctionType functionType = super.findType(functionRegistration, name); if (functionType == null) { - functionType = functionByNameExist(name) - ? new FunctionType(functionRegistration.getTarget().getClass()) + functionType = functionByNameExist(name) ? new FunctionType(functionRegistration.getTarget().getClass()) : this.findType(name); } @@ -179,9 +178,8 @@ public class ContextFunctionCatalogAutoConfiguration { */ @Deprecated @SuppressWarnings("rawtypes") - Set> merge(Map initial, - Map consumers, Map suppliers, - Map functions) { + Set> merge(Map initial, Map consumers, + Map suppliers, Map functions) { this.doMerge(initial, consumers, suppliers, functions); return null; } @@ -198,14 +196,13 @@ public class ContextFunctionCatalogAutoConfiguration { } private String getQualifier(String key) { - if (this.beanFactory != null - && this.beanFactory.containsBeanDefinition(key)) { + if (this.beanFactory != null && this.beanFactory.containsBeanDefinition(key)) { BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(key); Object source = beanDefinition.getSource(); if (source instanceof StandardMethodMetadata) { StandardMethodMetadata metadata = (StandardMethodMetadata) source; - Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation( - metadata.getIntrospectedMethod(), Qualifier.class); + Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation(metadata.getIntrospectedMethod(), + Qualifier.class); if (qualifier != null && qualifier.value().length() > 0) { return qualifier.value(); } @@ -215,8 +212,7 @@ public class ContextFunctionCatalogAutoConfiguration { } private boolean functionByNameExist(String name) { - return name == null || this.beanFactory == null - || !this.beanFactory.containsBeanDefinition(name); + return name == null || this.beanFactory == null || !this.beanFactory.containsBeanDefinition(name); } @SuppressWarnings("rawtypes") @@ -236,21 +232,20 @@ public class ContextFunctionCatalogAutoConfiguration { targets.put(registration.getTarget(), key); } - Stream.concat(consumerBeans.entrySet().stream(), Stream.concat( - supplierBeans.entrySet().stream(), functionBeans.entrySet().stream())) + Stream.concat(consumerBeans.entrySet().stream(), + Stream.concat(supplierBeans.entrySet().stream(), functionBeans.entrySet().stream())) .forEach(entry -> { if (!targets.containsKey(entry.getValue())) { - FunctionRegistration target = new FunctionRegistration( - entry.getValue(), + FunctionRegistration target = new FunctionRegistration(entry.getValue(), getAliases(entry.getKey()).toArray(new String[] {})); targets.put(target.getTarget(), entry.getKey()); registrations.add(target); } }); - registrations.forEach(registration -> register(registration, - targets.get(registration.getTarget()))); + registrations.forEach(registration -> register(registration, targets.get(registration.getTarget()))); } + } private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition { @@ -271,7 +266,7 @@ public class ContextFunctionCatalogAutoConfiguration { } - @Configuration + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Gson.class) @ConditionalOnBean(Gson.class) @Conditional(PreferGsonOrMissingJacksonCondition.class) @@ -284,7 +279,7 @@ public class ContextFunctionCatalogAutoConfiguration { } - @Configuration + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class) @ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, // 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 index 9e08e37ae..7186f772c 100644 --- 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 @@ -55,8 +55,7 @@ import org.springframework.util.ClassUtils; * @author Dave Syer * */ -public class ContextFunctionCatalogInitializer - implements ApplicationContextInitializer { +public class ContextFunctionCatalogInitializer implements ApplicationContextInitializer { /** * Property name for ignoring pre initilizer. @@ -70,32 +69,27 @@ public class ContextFunctionCatalogInitializer @Override public void initialize(GenericApplicationContext applicationContext) { - if (enabled && applicationContext.getEnvironment() - .getProperty("spring.functional.enabled", Boolean.class, false)) { - ContextFunctionCatalogBeanRegistrar registrar = new ContextFunctionCatalogBeanRegistrar( - applicationContext); + if (enabled + && applicationContext.getEnvironment().getProperty("spring.functional.enabled", Boolean.class, false)) { + ContextFunctionCatalogBeanRegistrar registrar = new ContextFunctionCatalogBeanRegistrar(applicationContext); applicationContext.addBeanFactoryPostProcessor(registrar); } } - static class ContextFunctionCatalogBeanRegistrar - implements BeanDefinitionRegistryPostProcessor { + static class ContextFunctionCatalogBeanRegistrar implements BeanDefinitionRegistryPostProcessor { private GenericApplicationContext context; - ContextFunctionCatalogBeanRegistrar( - GenericApplicationContext applicationContext) { + ContextFunctionCatalogBeanRegistrar(GenericApplicationContext applicationContext) { this.context = applicationContext; } @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) - throws BeansException { + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { try { register(registry, this.context.getDefaultListableBeanFactory()); } @@ -110,52 +104,41 @@ public class ContextFunctionCatalogInitializer } } - protected void register(BeanDefinitionRegistry registry, - ConfigurableListableBeanFactory factory) throws Exception { + protected void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory factory) + throws Exception { performPreinitialization(); - if (this.context.getBeanFactory().getBeanNamesForType( - PropertySourcesPlaceholderConfigurer.class, false, + if (this.context.getBeanFactory().getBeanNamesForType(PropertySourcesPlaceholderConfigurer.class, false, false).length == 0) { this.context.registerBean(PropertySourcesPlaceholderConfigurer.class, - () -> PropertyPlaceholderAutoConfiguration - .propertySourcesPlaceholderConfigurer()); + () -> PropertyPlaceholderAutoConfiguration.propertySourcesPlaceholderConfigurer()); } - if (!this.context.getBeanFactory().containsBean( - AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { + if (!this.context.getBeanFactory() + .containsBean(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { // Switch off the ConfigurationClassPostProcessor - this.context.registerBean( - AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME, + this.context.registerBean(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME, DummyProcessor.class, () -> new DummyProcessor()); // But switch on other annotation processing AnnotationConfigUtils.registerAnnotationConfigProcessors(this.context); } - if (!this.context.getBeanFactory().containsBean( - ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { - new ConfigurationPropertiesBindingPostProcessorRegistrar() - .registerBeanDefinitions(null, context); + if (!this.context.getBeanFactory().containsBean(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { + new ConfigurationPropertiesBindingPostProcessorRegistrar().registerBeanDefinitions(null, this.context); } - if (ClassUtils.isPresent("com.google.gson.Gson", null) - && "gson".equals(this.context.getEnvironment().getProperty( - ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, - "gson"))) { - if (this.context.getBeanFactory().getBeanNamesForType(Gson.class, false, - false).length == 0) { + if (ClassUtils.isPresent("com.google.gson.Gson", null) && "gson".equals(this.context.getEnvironment() + .getProperty(ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, "gson"))) { + if (this.context.getBeanFactory().getBeanNamesForType(Gson.class, false, false).length == 0) { this.context.registerBean(Gson.class, () -> new Gson()); } this.context.registerBean(JsonMapper.class, () -> new ContextFunctionCatalogAutoConfiguration.GsonConfiguration() .jsonMapper(this.context.getBean(Gson.class))); } - else if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", - null)) { - if (this.context.getBeanFactory().getBeanNamesForType(ObjectMapper.class, - false, false).length == 0) { - this.context.registerBean(ObjectMapper.class, - () -> new ObjectMapper()); + else if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) { + if (this.context.getBeanFactory().getBeanNamesForType(ObjectMapper.class, false, false).length == 0) { + this.context.registerBean(ObjectMapper.class, () -> new ObjectMapper()); } this.context.registerBean(JsonMapper.class, () -> new ContextFunctionCatalogAutoConfiguration.JacksonConfiguration() @@ -163,35 +146,29 @@ public class ContextFunctionCatalogInitializer } - String basePackage = this.context.getEnvironment() - .getProperty("spring.cloud.function.scan.packages", "functions"); - if (new ClassPathResource(basePackage.replace(".", "/")).exists()) { - ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner( - this.context, false, this.context.getEnvironment(), this.context); + String basePackage = this.context.getEnvironment().getProperty("spring.cloud.function.scan.packages", + "functions"); + if (this.context.getEnvironment().getProperty("spring.cloud.function.scan.enabled", Boolean.class, true) + && new ClassPathResource(basePackage.replace(".", "/")).exists()) { + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.context, false, + this.context.getEnvironment(), this.context); scanner.addIncludeFilter(new AssignableTypeFilter(Function.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Supplier.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Consumer.class)); for (BeanDefinition bean : scanner.findCandidateComponents(basePackage)) { String name = bean.getBeanClassName(); - Class type = ClassUtils.resolveClassName(name, - this.context.getClassLoader()); + Class type = ClassUtils.resolveClassName(name, this.context.getClassLoader()); this.context.registerBeanDefinition(name, bean); - this.context.registerBean("registration_" + name, - FunctionRegistration.class, - () -> new FunctionRegistration<>(this.context.getBean(name), - name).type(type)); + this.context.registerBean("registration_" + name, FunctionRegistration.class, + () -> new FunctionRegistration<>(this.context.getBean(name), name).type(type)); } } - if (this.context.getBeanFactory().getBeanNamesForType(FunctionCatalog.class, - false, false).length == 0) { - this.context.registerBean(InMemoryFunctionCatalog.class, - () -> new InMemoryFunctionCatalog()); - this.context - .registerBean(FunctionRegistrationPostProcessor.class, - () -> new FunctionRegistrationPostProcessor(this.context - .getAutowireCapableBeanFactory() - .getBeanProvider(FunctionRegistration.class))); + if (this.context.getBeanFactory().getBeanNamesForType(FunctionCatalog.class, false, false).length == 0) { + this.context.registerBean(InMemoryFunctionCatalog.class, () -> new InMemoryFunctionCatalog()); + this.context.registerBean(FunctionRegistrationPostProcessor.class, + () -> new FunctionRegistrationPostProcessor(this.context.getAutowireCapableBeanFactory() + .getBeanProvider(FunctionRegistration.class))); } } @@ -234,8 +211,7 @@ public class ContextFunctionCatalogInitializer } @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof FunctionRegistry) { FunctionRegistry catalog = (FunctionRegistry) bean; for (FunctionRegistration registration : this.functions) { @@ -243,8 +219,7 @@ public class ContextFunctionCatalogInitializer "FunctionRegistration must define at least one name. Was empty"); if (registration.getType() == null) { throw new IllegalStateException( - "You need an explicit type for the function: " - + registration.getNames()); + "You need an explicit type for the function: " + registration.getNames()); // 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.