Add flag for disabling component scan

This commit is contained in:
Dave Syer
2019-07-10 17:08:41 +01:00
committed by Oleg Zhurakousky
parent 307292b959
commit b4f5c0339f
3 changed files with 86 additions and 105 deletions

View File

@@ -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 function that returns a publisher, so that it can be subscribed to in
a controlled way. 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 === Function Routing
Since version 2.2 Spring Cloud Function provides routing feature allowing Since version 2.2 Spring Cloud Function provides routing feature allowing

View File

@@ -78,11 +78,8 @@ import org.springframework.messaging.converter.StringMessageConverter;
* @author Artem Bilan * @author Artem Bilan
* @author Anshul Mehra * @author Anshul Mehra
*/ */
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(FunctionCatalog.class) @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 { public class ContextFunctionCatalogAutoConfiguration {
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper"; 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); return new RoutingFunction(functionCatalog, functionInspector, messageConverter);
} }
protected static class BeanFactoryFunctionCatalog @Configuration(proxyBeanMethods = false)
extends AbstractComposableFunctionRegistry @ComponentScan(basePackages = "${spring.cloud.function.scan.packages:functions}", //
implements SmartInitializingSingleton, BeanFactoryAware { 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; private ApplicationEventPublisher applicationEventPublisher;
@@ -118,16 +124,12 @@ public class ContextFunctionCatalogAutoConfiguration {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void afterSingletonsInstantiated() { public void afterSingletonsInstantiated() {
Map<String, Supplier> supplierBeans = this.beanFactory Map<String, Supplier> supplierBeans = this.beanFactory.getBeansOfType(Supplier.class);
.getBeansOfType(Supplier.class); Map<String, Function> functionBeans = this.beanFactory.getBeansOfType(Function.class);
Map<String, Function> functionBeans = this.beanFactory Map<String, Consumer> consumerBeans = this.beanFactory.getBeansOfType(Consumer.class);
.getBeansOfType(Function.class);
Map<String, Consumer> consumerBeans = this.beanFactory
.getBeansOfType(Consumer.class);
Map<String, FunctionRegistration> functionRegistrationBeans = this.beanFactory Map<String, FunctionRegistration> functionRegistrationBeans = this.beanFactory
.getBeansOfType(FunctionRegistration.class); .getBeansOfType(FunctionRegistration.class);
this.doMerge(functionRegistrationBeans, consumerBeans, supplierBeans, this.doMerge(functionRegistrationBeans, consumerBeans, supplierBeans, functionBeans);
functionBeans);
} }
@Override @Override
@@ -139,14 +141,12 @@ public class ContextFunctionCatalogAutoConfiguration {
public void close() { public void close() {
if (this.applicationEventPublisher != null) { if (this.applicationEventPublisher != null) {
if (this.hasFunctions()) { if (this.hasFunctions()) {
this.applicationEventPublisher this.applicationEventPublisher.publishEvent(
.publishEvent(new FunctionUnregistrationEvent(this, new FunctionUnregistrationEvent(this, Function.class, this.getFunctionNames()));
Function.class, this.getFunctionNames()));
} }
if (this.hasSuppliers()) { if (this.hasSuppliers()) {
this.applicationEventPublisher this.applicationEventPublisher.publishEvent(
.publishEvent(new FunctionUnregistrationEvent(this, new FunctionUnregistrationEvent(this, Supplier.class, this.getSupplierNames()));
Supplier.class, this.getSupplierNames()));
} }
} }
} }
@@ -155,8 +155,7 @@ public class ContextFunctionCatalogAutoConfiguration {
protected FunctionType findType(FunctionRegistration<?> functionRegistration, String name) { protected FunctionType findType(FunctionRegistration<?> functionRegistration, String name) {
FunctionType functionType = super.findType(functionRegistration, name); FunctionType functionType = super.findType(functionRegistration, name);
if (functionType == null) { if (functionType == null) {
functionType = functionByNameExist(name) functionType = functionByNameExist(name) ? new FunctionType(functionRegistration.getTarget().getClass())
? new FunctionType(functionRegistration.getTarget().getClass())
: this.findType(name); : this.findType(name);
} }
@@ -179,9 +178,8 @@ public class ContextFunctionCatalogAutoConfiguration {
*/ */
@Deprecated @Deprecated
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Set<FunctionRegistration<?>> merge(Map<String, FunctionRegistration> initial, Set<FunctionRegistration<?>> merge(Map<String, FunctionRegistration> initial, Map<String, Consumer> consumers,
Map<String, Consumer> consumers, Map<String, Supplier> suppliers, Map<String, Supplier> suppliers, Map<String, Function> functions) {
Map<String, Function> functions) {
this.doMerge(initial, consumers, suppliers, functions); this.doMerge(initial, consumers, suppliers, functions);
return null; return null;
} }
@@ -198,14 +196,13 @@ public class ContextFunctionCatalogAutoConfiguration {
} }
private String getQualifier(String key) { private String getQualifier(String key) {
if (this.beanFactory != null if (this.beanFactory != null && this.beanFactory.containsBeanDefinition(key)) {
&& this.beanFactory.containsBeanDefinition(key)) {
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(key); BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(key);
Object source = beanDefinition.getSource(); Object source = beanDefinition.getSource();
if (source instanceof StandardMethodMetadata) { if (source instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) source; StandardMethodMetadata metadata = (StandardMethodMetadata) source;
Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation( Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation(metadata.getIntrospectedMethod(),
metadata.getIntrospectedMethod(), Qualifier.class); Qualifier.class);
if (qualifier != null && qualifier.value().length() > 0) { if (qualifier != null && qualifier.value().length() > 0) {
return qualifier.value(); return qualifier.value();
} }
@@ -215,8 +212,7 @@ public class ContextFunctionCatalogAutoConfiguration {
} }
private boolean functionByNameExist(String name) { private boolean functionByNameExist(String name) {
return name == null || this.beanFactory == null return name == null || this.beanFactory == null || !this.beanFactory.containsBeanDefinition(name);
|| !this.beanFactory.containsBeanDefinition(name);
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@@ -236,21 +232,20 @@ public class ContextFunctionCatalogAutoConfiguration {
targets.put(registration.getTarget(), key); targets.put(registration.getTarget(), key);
} }
Stream.concat(consumerBeans.entrySet().stream(), Stream.concat( Stream.concat(consumerBeans.entrySet().stream(),
supplierBeans.entrySet().stream(), functionBeans.entrySet().stream())) Stream.concat(supplierBeans.entrySet().stream(), functionBeans.entrySet().stream()))
.forEach(entry -> { .forEach(entry -> {
if (!targets.containsKey(entry.getValue())) { if (!targets.containsKey(entry.getValue())) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>( FunctionRegistration<Object> target = new FunctionRegistration<Object>(entry.getValue(),
entry.getValue(),
getAliases(entry.getKey()).toArray(new String[] {})); getAliases(entry.getKey()).toArray(new String[] {}));
targets.put(target.getTarget(), entry.getKey()); targets.put(target.getTarget(), entry.getKey());
registrations.add(target); registrations.add(target);
} }
}); });
registrations.forEach(registration -> register(registration, registrations.forEach(registration -> register(registration, targets.get(registration.getTarget())));
targets.get(registration.getTarget())));
} }
} }
private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition { private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition {
@@ -271,7 +266,7 @@ public class ContextFunctionCatalogAutoConfiguration {
} }
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Gson.class) @ConditionalOnClass(Gson.class)
@ConditionalOnBean(Gson.class) @ConditionalOnBean(Gson.class)
@Conditional(PreferGsonOrMissingJacksonCondition.class) @Conditional(PreferGsonOrMissingJacksonCondition.class)
@@ -284,7 +279,7 @@ public class ContextFunctionCatalogAutoConfiguration {
} }
@Configuration @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class) @ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class) @ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, // @ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, //

View File

@@ -55,8 +55,7 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer * @author Dave Syer
* *
*/ */
public class ContextFunctionCatalogInitializer public class ContextFunctionCatalogInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
implements ApplicationContextInitializer<GenericApplicationContext> {
/** /**
* Property name for ignoring pre initilizer. * Property name for ignoring pre initilizer.
@@ -70,32 +69,27 @@ public class ContextFunctionCatalogInitializer
@Override @Override
public void initialize(GenericApplicationContext applicationContext) { public void initialize(GenericApplicationContext applicationContext) {
if (enabled && applicationContext.getEnvironment() if (enabled
.getProperty("spring.functional.enabled", Boolean.class, false)) { && applicationContext.getEnvironment().getProperty("spring.functional.enabled", Boolean.class, false)) {
ContextFunctionCatalogBeanRegistrar registrar = new ContextFunctionCatalogBeanRegistrar( ContextFunctionCatalogBeanRegistrar registrar = new ContextFunctionCatalogBeanRegistrar(applicationContext);
applicationContext);
applicationContext.addBeanFactoryPostProcessor(registrar); applicationContext.addBeanFactoryPostProcessor(registrar);
} }
} }
static class ContextFunctionCatalogBeanRegistrar static class ContextFunctionCatalogBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
implements BeanDefinitionRegistryPostProcessor {
private GenericApplicationContext context; private GenericApplicationContext context;
ContextFunctionCatalogBeanRegistrar( ContextFunctionCatalogBeanRegistrar(GenericApplicationContext applicationContext) {
GenericApplicationContext applicationContext) {
this.context = applicationContext; this.context = applicationContext;
} }
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
throws BeansException {
} }
@Override @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
throws BeansException {
try { try {
register(registry, this.context.getDefaultListableBeanFactory()); register(registry, this.context.getDefaultListableBeanFactory());
} }
@@ -110,52 +104,41 @@ public class ContextFunctionCatalogInitializer
} }
} }
protected void register(BeanDefinitionRegistry registry, protected void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory factory)
ConfigurableListableBeanFactory factory) throws Exception { throws Exception {
performPreinitialization(); performPreinitialization();
if (this.context.getBeanFactory().getBeanNamesForType( if (this.context.getBeanFactory().getBeanNamesForType(PropertySourcesPlaceholderConfigurer.class, false,
PropertySourcesPlaceholderConfigurer.class, false,
false).length == 0) { false).length == 0) {
this.context.registerBean(PropertySourcesPlaceholderConfigurer.class, this.context.registerBean(PropertySourcesPlaceholderConfigurer.class,
() -> PropertyPlaceholderAutoConfiguration () -> PropertyPlaceholderAutoConfiguration.propertySourcesPlaceholderConfigurer());
.propertySourcesPlaceholderConfigurer());
} }
if (!this.context.getBeanFactory().containsBean( if (!this.context.getBeanFactory()
AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { .containsBean(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
// Switch off the ConfigurationClassPostProcessor // Switch off the ConfigurationClassPostProcessor
this.context.registerBean( this.context.registerBean(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME,
AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME,
DummyProcessor.class, () -> new DummyProcessor()); DummyProcessor.class, () -> new DummyProcessor());
// But switch on other annotation processing // But switch on other annotation processing
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.context); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.context);
} }
if (!this.context.getBeanFactory().containsBean( if (!this.context.getBeanFactory().containsBean(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { new ConfigurationPropertiesBindingPostProcessorRegistrar().registerBeanDefinitions(null, this.context);
new ConfigurationPropertiesBindingPostProcessorRegistrar()
.registerBeanDefinitions(null, context);
} }
if (ClassUtils.isPresent("com.google.gson.Gson", null) if (ClassUtils.isPresent("com.google.gson.Gson", null) && "gson".equals(this.context.getEnvironment()
&& "gson".equals(this.context.getEnvironment().getProperty( .getProperty(ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, "gson"))) {
ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, if (this.context.getBeanFactory().getBeanNamesForType(Gson.class, false, false).length == 0) {
"gson"))) {
if (this.context.getBeanFactory().getBeanNamesForType(Gson.class, false,
false).length == 0) {
this.context.registerBean(Gson.class, () -> new Gson()); this.context.registerBean(Gson.class, () -> new Gson());
} }
this.context.registerBean(JsonMapper.class, this.context.registerBean(JsonMapper.class,
() -> new ContextFunctionCatalogAutoConfiguration.GsonConfiguration() () -> new ContextFunctionCatalogAutoConfiguration.GsonConfiguration()
.jsonMapper(this.context.getBean(Gson.class))); .jsonMapper(this.context.getBean(Gson.class)));
} }
else if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", else if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
null)) { if (this.context.getBeanFactory().getBeanNamesForType(ObjectMapper.class, false, false).length == 0) {
if (this.context.getBeanFactory().getBeanNamesForType(ObjectMapper.class, this.context.registerBean(ObjectMapper.class, () -> new ObjectMapper());
false, false).length == 0) {
this.context.registerBean(ObjectMapper.class,
() -> new ObjectMapper());
} }
this.context.registerBean(JsonMapper.class, this.context.registerBean(JsonMapper.class,
() -> new ContextFunctionCatalogAutoConfiguration.JacksonConfiguration() () -> new ContextFunctionCatalogAutoConfiguration.JacksonConfiguration()
@@ -163,35 +146,29 @@ public class ContextFunctionCatalogInitializer
} }
String basePackage = this.context.getEnvironment() String basePackage = this.context.getEnvironment().getProperty("spring.cloud.function.scan.packages",
.getProperty("spring.cloud.function.scan.packages", "functions"); "functions");
if (new ClassPathResource(basePackage.replace(".", "/")).exists()) { if (this.context.getEnvironment().getProperty("spring.cloud.function.scan.enabled", Boolean.class, true)
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner( && new ClassPathResource(basePackage.replace(".", "/")).exists()) {
this.context, false, this.context.getEnvironment(), this.context); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.context, false,
this.context.getEnvironment(), this.context);
scanner.addIncludeFilter(new AssignableTypeFilter(Function.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Function.class));
scanner.addIncludeFilter(new AssignableTypeFilter(Supplier.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Supplier.class));
scanner.addIncludeFilter(new AssignableTypeFilter(Consumer.class)); scanner.addIncludeFilter(new AssignableTypeFilter(Consumer.class));
for (BeanDefinition bean : scanner.findCandidateComponents(basePackage)) { for (BeanDefinition bean : scanner.findCandidateComponents(basePackage)) {
String name = bean.getBeanClassName(); String name = bean.getBeanClassName();
Class<?> type = ClassUtils.resolveClassName(name, Class<?> type = ClassUtils.resolveClassName(name, this.context.getClassLoader());
this.context.getClassLoader());
this.context.registerBeanDefinition(name, bean); this.context.registerBeanDefinition(name, bean);
this.context.registerBean("registration_" + name, this.context.registerBean("registration_" + name, FunctionRegistration.class,
FunctionRegistration.class, () -> new FunctionRegistration<>(this.context.getBean(name), name).type(type));
() -> new FunctionRegistration<>(this.context.getBean(name),
name).type(type));
} }
} }
if (this.context.getBeanFactory().getBeanNamesForType(FunctionCatalog.class, if (this.context.getBeanFactory().getBeanNamesForType(FunctionCatalog.class, false, false).length == 0) {
false, false).length == 0) { this.context.registerBean(InMemoryFunctionCatalog.class, () -> new InMemoryFunctionCatalog());
this.context.registerBean(InMemoryFunctionCatalog.class, this.context.registerBean(FunctionRegistrationPostProcessor.class,
() -> new InMemoryFunctionCatalog()); () -> new FunctionRegistrationPostProcessor(this.context.getAutowireCapableBeanFactory()
this.context .getBeanProvider(FunctionRegistration.class)));
.registerBean(FunctionRegistrationPostProcessor.class,
() -> new FunctionRegistrationPostProcessor(this.context
.getAutowireCapableBeanFactory()
.getBeanProvider(FunctionRegistration.class)));
} }
} }
@@ -234,8 +211,7 @@ public class ContextFunctionCatalogInitializer
} }
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
throws BeansException {
if (bean instanceof FunctionRegistry) { if (bean instanceof FunctionRegistry) {
FunctionRegistry catalog = (FunctionRegistry) bean; FunctionRegistry catalog = (FunctionRegistry) bean;
for (FunctionRegistration<?> registration : this.functions) { for (FunctionRegistration<?> registration : this.functions) {
@@ -243,8 +219,7 @@ public class ContextFunctionCatalogInitializer
"FunctionRegistration must define at least one name. Was empty"); "FunctionRegistration must define at least one name. Was empty");
if (registration.getType() == null) { if (registration.getType() == null) {
throw new IllegalStateException( throw new IllegalStateException(
"You need an explicit type for the function: " "You need an explicit type for the function: " + registration.getNames());
+ registration.getNames());
// TODO: in principle Spring could know how to extract this // TODO: in principle Spring could know how to extract this
// from the supplier, but in practice there is no functional // from the supplier, but in practice there is no functional
// bean registration with parametric types. // bean registration with parametric types.