GH-3926: BeanNameGenerator for @MessagingGateway (#3927)
* GH-3926: BeanNameGenerator for @MessagingGateway Fixes https://github.com/spring-projects/spring-integration/issues/3926 Current approach for generated bean name in the `MessagingGatewayRegistrar` is to decapitalize simple class name, which is similar to standard `AnnotationBeanNameGenerator` * Make logic in the `MessagingGatewayRegistrar` based on the provided `BeanNameGenerator` * Expose an `@IntegrationComponentScan.nameGenerator()` attribute to allow to customize default bean name generation strategy * Introduce `IntegrationConfigUtils.annotationBeanNameGenerator()` to take a provided `AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR` singleton or fallback to the `AnnotationBeanNameGenerator.INSTANCE` * Use this utility in the `IntegrationComponentScanRegistrar` if no custom strategy is provided in the `@IntegrationComponentScan` * Use same util from the `GatewayParser` since there is no custom naming strategy configuration * Some other current Java level refactoring in the `IntegrationComponentScanRegistrar` and `MessagingGatewayRegistrar` * * Meta-annotate `@MessagingGateway` with a `@MessageEndpoint` * Alias `@MessageEndpoint.value()` with a `@Component.value()` * Alias `@MessagingGateway.name()` with a `@MessageEndpoint.value()` * * Remove unused imports * * Replace `MessagingGatewayRegistrar` parsing logic for annotation configuration directly by the `GatewayProxyInstantiationPostProcessor` and `AnnotationGatewayProxyFactoryBean`. * The `MessagingGatewayRegistrar` logic has been migrated back to the `GatewayParser`, but only with an XML-relevant parts * Change the logic of the `IntegrationComponentScanRegistrar` to rely on a `ClassPathBeanDefinitionScanner` and its `scan()` functionality since this is all what we need to trigger a `GatewayProxyInstantiationPostProcessor` for scanned components * Fix `GatewayProxyInstantiationPostProcessor` to call an `afterPropertiesSet()` as well * Add a `value()` alias attribute for the `@MessagingGateway` to satisfy a name resolution from a `@Component` * Replace annotation chain resolution logic by new `MessagingAnnotationUtils.resolveMergedAttribute()` API * Use `MergedAnnotations` API in the `AnnotationGatewayProxyFactoryBean` to preserve a logic for attribute resolution from the annotation hierarchy * Add expression resolution for attribute values in the `AnnotationGatewayProxyFactoryBean` * Make a `GatewayInterfaceTests.CustomBeanNameGenerator` as an `AnnotationBeanNameGenerator` extension to satisfy the test logic expectations: no custom name if explicit is present * Clean up some doc typos after merge conflict The fix in this commit essentially resolves some old JIRA ticket: https://jira.spring.io/browse/INT-4558 * * Remove redundant `MessagingAnnotationUtils.resolveMergedAttribute()` * Optimize the logic in the `AnnotationGatewayProxyFactoryBean` around annotation attributes to plain annotation - no adaptation to maps
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
@@ -105,4 +106,17 @@ public @interface IntegrationComponentScan {
|
||||
*/
|
||||
Filter[] excludeFilters() default { };
|
||||
|
||||
|
||||
/**
|
||||
* The {@link BeanNameGenerator} class to be used for naming detected Spring Integration components.
|
||||
* <p>The default value of the {@link BeanNameGenerator} interface itself indicates
|
||||
* that the scanner used to process this {@link IntegrationComponentScan} annotation should
|
||||
* use its inherited bean name generator, e.g. the default
|
||||
* {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}
|
||||
* or any custom instance supplied to the application context at bootstrap time.
|
||||
* @since 6.0
|
||||
* @see org.springframework.context.annotation.ComponentScan#nameGenerator()
|
||||
*/
|
||||
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -23,6 +23,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -30,6 +31,7 @@ import org.springframework.stereotype.Component;
|
||||
* Message Endpoint.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -41,9 +43,9 @@ public @interface MessageEndpoint {
|
||||
/**
|
||||
* The value may indicate a suggestion for a logical component name,
|
||||
* to be turned into a Spring bean in case of an autodetected component.
|
||||
*
|
||||
* @return the suggested component name, if any
|
||||
*/
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -21,12 +21,12 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.stereotype.Indexed;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* A stereotype annotation to provide an Integration Messaging Gateway Proxy
|
||||
* ({@code <gateway/>}) as an abstraction over the messaging API. The target
|
||||
* application's business logic may be completely unaware of the Spring Integration
|
||||
* as an abstraction over the messaging API. The target application's
|
||||
* business logic may be completely unaware of the Spring Integration
|
||||
* API, with the code interacting only via the interface.
|
||||
* <p>
|
||||
* Important: The {@link IntegrationComponentScan} annotation is required along with
|
||||
@@ -41,17 +41,28 @@ import org.springframework.stereotype.Indexed;
|
||||
* @since 4.0
|
||||
*
|
||||
* @see IntegrationComponentScan
|
||||
* @see MessageEndpoint
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Indexed
|
||||
@MessageEndpoint
|
||||
public @interface MessagingGateway {
|
||||
|
||||
/**
|
||||
* The value may indicate a suggestion for a logical component name,
|
||||
* to be turned into a Spring bean in case of an autodetected component.
|
||||
* @return the suggested component name, if any
|
||||
* @since 6.0
|
||||
*/
|
||||
@AliasFor(annotation = MessageEndpoint.class)
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The value may indicate a suggestion for a logical component name,
|
||||
* to be turned into a Spring bean in case of an autodetected component.
|
||||
* @return the suggested component name, if any
|
||||
*/
|
||||
@AliasFor(annotation = MessageEndpoint.class, attribute = "value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
@@ -90,7 +101,7 @@ public @interface MessagingGateway {
|
||||
|
||||
/**
|
||||
* Allows to specify how long this gateway will wait for the reply {@code Message}
|
||||
* before returning. By default it will wait indefinitely. {@code null} is returned if
|
||||
* before returning. By default, it will wait indefinitely. {@code null} is returned if
|
||||
* the gateway times out. Value is specified in milliseconds; it can be a simple long
|
||||
* value or a SpEL expression; array variable #args is available.
|
||||
* @return the suggested timeout in milliseconds, if any
|
||||
|
||||
@@ -18,10 +18,12 @@ package org.springframework.integration.config;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.gateway.AnnotationGatewayProxyFactoryBean;
|
||||
@@ -54,20 +56,23 @@ class GatewayProxyInstantiationPostProcessor implements
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
|
||||
if (beanClass.isInterface()
|
||||
&& AnnotatedElementUtils.hasAnnotation(beanClass, MessagingGateway.class)
|
||||
&& this.registry.getBeanDefinition(beanName) instanceof AnnotatedGenericBeanDefinition) {
|
||||
if (beanClass.isInterface() && AnnotatedElementUtils.hasAnnotation(beanClass, MessagingGateway.class)) {
|
||||
BeanDefinition beanDefinition = this.registry.getBeanDefinition(beanName);
|
||||
if (beanDefinition instanceof AnnotatedGenericBeanDefinition
|
||||
|| beanDefinition instanceof ScannedGenericBeanDefinition) {
|
||||
|
||||
AnnotationGatewayProxyFactoryBean<?> gatewayProxyFactoryBean =
|
||||
new AnnotationGatewayProxyFactoryBean<>(beanClass);
|
||||
gatewayProxyFactoryBean.setApplicationContext(this.applicationContext);
|
||||
gatewayProxyFactoryBean.setBeanFactory(this.applicationContext.getAutowireCapableBeanFactory());
|
||||
ClassLoader classLoader = this.applicationContext.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
gatewayProxyFactoryBean.setBeanClassLoader(classLoader);
|
||||
AnnotationGatewayProxyFactoryBean<?> gatewayProxyFactoryBean =
|
||||
new AnnotationGatewayProxyFactoryBean<>(beanClass);
|
||||
gatewayProxyFactoryBean.setApplicationContext(this.applicationContext);
|
||||
gatewayProxyFactoryBean.setBeanFactory(this.applicationContext.getAutowireCapableBeanFactory());
|
||||
ClassLoader classLoader = this.applicationContext.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
gatewayProxyFactoryBean.setBeanClassLoader(classLoader);
|
||||
}
|
||||
gatewayProxyFactoryBean.setBeanName(beanName);
|
||||
gatewayProxyFactoryBean.afterPropertiesSet();
|
||||
return gatewayProxyFactoryBean;
|
||||
}
|
||||
gatewayProxyFactoryBean.setBeanName(beanName);
|
||||
return gatewayProxyFactoryBean;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 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.
|
||||
@@ -20,10 +20,8 @@ import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -33,11 +31,12 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
@@ -67,15 +66,14 @@ import org.springframework.util.StringUtils;
|
||||
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
ResourceLoaderAware, EnvironmentAware {
|
||||
|
||||
private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars = new HashMap<>();
|
||||
private final List<TypeFilter> defaultFilters = new ArrayList<>();
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public IntegrationComponentScanRegistrar() {
|
||||
this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true),
|
||||
new MessagingGatewayRegistrar());
|
||||
this.defaultFilters.add(new AnnotationTypeFilter(MessagingGateway.class, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,84 +88,75 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> componentScan =
|
||||
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName());
|
||||
AnnotationAttributes componentScan =
|
||||
AnnotationAttributes.fromMap(
|
||||
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName()));
|
||||
|
||||
Collection<String> basePackages = getBasePackages(importingClassMetadata, registry);
|
||||
Collection<String> basePackages = getBasePackages(componentScan, registry);
|
||||
|
||||
if (basePackages.isEmpty()) {
|
||||
basePackages = Collections.singleton(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
|
||||
}
|
||||
|
||||
ClassPathScanningCandidateComponentProvider scanner =
|
||||
new ClassPathScanningCandidateComponentProvider(false, this.environment) {
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false) {
|
||||
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
return beanDefinition.getMetadata().isIndependent()
|
||||
&& !beanDefinition.getMetadata().isAnnotation();
|
||||
}
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
return beanDefinition.getMetadata().isIndependent()
|
||||
&& !beanDefinition.getMetadata().isAnnotation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionRegistry getRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
filter(registry, componentScan, scanner); // NOSONAR - never null
|
||||
|
||||
scanner.setResourceLoader(this.resourceLoader);
|
||||
scanner.setEnvironment(this.environment);
|
||||
|
||||
for (String basePackage : basePackages) {
|
||||
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
|
||||
for (BeanDefinition candidateComponent : candidateComponents) {
|
||||
if (candidateComponent instanceof AnnotatedBeanDefinition) {
|
||||
for (ImportBeanDefinitionRegistrar registrar : this.componentRegistrars.values()) {
|
||||
registrar.registerBeanDefinitions(((AnnotatedBeanDefinition) candidateComponent).getMetadata(),
|
||||
registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
BeanNameGenerator beanNameGenerator = IntegrationConfigUtils.annotationBeanNameGenerator(registry);
|
||||
|
||||
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
|
||||
if (!(BeanNameGenerator.class == generatorClass)) {
|
||||
beanNameGenerator = BeanUtils.instantiateClass(generatorClass);
|
||||
}
|
||||
|
||||
scanner.setBeanNameGenerator(beanNameGenerator);
|
||||
scanner.scan(basePackages.toArray(String[]::new));
|
||||
}
|
||||
|
||||
private void filter(BeanDefinitionRegistry registry, Map<String, Object> componentScan,
|
||||
private void filter(BeanDefinitionRegistry registry, AnnotationAttributes componentScan,
|
||||
ClassPathScanningCandidateComponentProvider scanner) {
|
||||
|
||||
if ((boolean) componentScan.get("useDefaultFilters")) { // NOSONAR - never null
|
||||
for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {
|
||||
if (componentScan.getBoolean("useDefaultFilters")) { // NOSONAR - never null
|
||||
for (TypeFilter typeFilter : this.defaultFilters) {
|
||||
scanner.addIncludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
|
||||
for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("includeFilters")) {
|
||||
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
|
||||
scanner.addIncludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("excludeFilters")) {
|
||||
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
|
||||
for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
|
||||
scanner.addExcludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<String> getBasePackages(AnnotationMetadata importingClassMetadata,
|
||||
protected Collection<String> getBasePackages(AnnotationAttributes componentScan,
|
||||
@SuppressWarnings("unused") BeanDefinitionRegistry registry) {
|
||||
|
||||
Map<String, Object> componentScan =
|
||||
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName());
|
||||
|
||||
Set<String> basePackages = new HashSet<>();
|
||||
|
||||
for (String pkg : (String[]) componentScan.get("value")) { // NOSONAR - never null
|
||||
for (String pkg : componentScan.getStringArray("value")) {
|
||||
if (StringUtils.hasText(pkg)) {
|
||||
basePackages.add(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
for (Class<?> clazz : (Class<?>[]) componentScan.get("basePackageClasses")) {
|
||||
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
|
||||
basePackages.add(ClassUtils.getPackageName(clazz));
|
||||
}
|
||||
|
||||
@@ -180,38 +169,33 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
|
||||
|
||||
for (Class<?> filterClass : filter.getClassArray("classes")) {
|
||||
switch (filterType) {
|
||||
case ANNOTATION:
|
||||
case ANNOTATION -> {
|
||||
Assert.isAssignable(Annotation.class, filterClass,
|
||||
"An error occurred while processing a @IntegrationComponentScan ANNOTATION type filter: ");
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<Annotation> annotationType = (Class<Annotation>) filterClass;
|
||||
typeFilters.add(new AnnotationTypeFilter(annotationType));
|
||||
break;
|
||||
case ASSIGNABLE_TYPE:
|
||||
typeFilters.add(new AssignableTypeFilter(filterClass));
|
||||
break;
|
||||
case CUSTOM:
|
||||
}
|
||||
case ASSIGNABLE_TYPE -> typeFilters.add(new AssignableTypeFilter(filterClass));
|
||||
case CUSTOM -> {
|
||||
Assert.isAssignable(TypeFilter.class, filterClass,
|
||||
"An error occurred while processing a @IntegrationComponentScan CUSTOM type filter: ");
|
||||
TypeFilter typeFilter = BeanUtils.instantiateClass(filterClass, TypeFilter.class);
|
||||
invokeAwareMethods(filter, this.environment, this.resourceLoader, registry);
|
||||
typeFilters.add(typeFilter);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Filter type not supported with Class value: " +
|
||||
filterType);
|
||||
}
|
||||
}
|
||||
|
||||
for (String expression : filter.getStringArray("pattern")) {
|
||||
switch (filterType) {
|
||||
case ASPECTJ:
|
||||
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
|
||||
break;
|
||||
case REGEX:
|
||||
typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType);
|
||||
case ASPECTJ ->
|
||||
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
|
||||
case REGEX -> typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
|
||||
default -> throw new IllegalArgumentException("Filter type not supported with String pattern: "
|
||||
+ filterType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.springframework.integration.config;
|
||||
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
|
||||
/**
|
||||
@@ -64,6 +68,20 @@ public final class IntegrationConfigUtils {
|
||||
registry.registerBeanDefinition(channelName, new RootBeanDefinition(DirectChannel.class, DirectChannel::new));
|
||||
}
|
||||
|
||||
public static BeanNameGenerator annotationBeanNameGenerator(BeanDefinitionRegistry registry) {
|
||||
BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
|
||||
if (registry instanceof SingletonBeanRegistry singletonBeanRegistry) {
|
||||
BeanNameGenerator generator =
|
||||
(BeanNameGenerator) singletonBeanRegistry.getSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
if (generator != null) {
|
||||
beanNameGenerator = generator;
|
||||
}
|
||||
}
|
||||
|
||||
return beanNameGenerator;
|
||||
}
|
||||
|
||||
private IntegrationConfigUtils() {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2022 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.integration.config;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.gateway.GatewayMethodMetadata;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.RequestReplyExchanger;
|
||||
import org.springframework.integration.util.MessagingAnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The {@link ImportBeanDefinitionRegistrar} to parse {@link MessagingGateway} and its {@code service-interface}
|
||||
* and to register {@link BeanDefinition} {@link GatewayProxyFactoryBean}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Andy Wilksinson
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private static final String PROXY_DEFAULT_METHODS_ATTR = "proxyDefaultMethods";
|
||||
|
||||
private static final String PRIMARY_ATTR = "primary";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
|
||||
Assert.isTrue(importingClassMetadata.isInterface(),
|
||||
"@MessagingGateway can only be specified on an interface");
|
||||
List<MultiValueMap<String, Object>> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata);
|
||||
Map<String, Object> annotationAttributes =
|
||||
importingClassMetadata.getAnnotationAttributes(MessagingGateway.class.getName());
|
||||
replaceEmptyOverrides(valuesHierarchy, annotationAttributes); // NOSONAR never null
|
||||
annotationAttributes.put("serviceInterface", importingClassMetadata.getClassName());
|
||||
annotationAttributes.put(PROXY_DEFAULT_METHODS_ATTR,
|
||||
"" + annotationAttributes.remove(PROXY_DEFAULT_METHODS_ATTR));
|
||||
if (importingClassMetadata.isAnnotated(Primary.class.getName())) {
|
||||
annotationAttributes.put(PRIMARY_ATTR, true);
|
||||
}
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(parse(annotationAttributes, registry), registry);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes, BeanDefinitionRegistry registry) { // NOSONAR complexity
|
||||
String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object>[] defaultHeaders = (Map<String, Object>[]) gatewayAttributes.get("defaultHeaders");
|
||||
|
||||
String defaultRequestChannel = (String) gatewayAttributes.get("defaultRequestChannel");
|
||||
String defaultReplyChannel = (String) gatewayAttributes.get("defaultReplyChannel");
|
||||
String errorChannel = (String) gatewayAttributes.get("errorChannel");
|
||||
String asyncExecutor = (String) gatewayAttributes.get("asyncExecutor");
|
||||
String mapper = (String) gatewayAttributes.get("mapper");
|
||||
String proxyDefaultMethods = (String) gatewayAttributes.get(PROXY_DEFAULT_METHODS_ATTR);
|
||||
|
||||
boolean hasMapper = StringUtils.hasText(mapper);
|
||||
boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression);
|
||||
Assert.state(!hasMapper || !hasDefaultPayloadExpression,
|
||||
"'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
|
||||
boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders);
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders,
|
||||
"'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
|
||||
ConfigurableBeanFactory beanFactory = obtainBeanFactory(registry);
|
||||
Class<?> serviceInterface = getServiceInterface((String) gatewayAttributes.get("serviceInterface"), beanFactory);
|
||||
|
||||
BeanDefinitionBuilder gatewayProxyBuilder =
|
||||
BeanDefinitionBuilder.rootBeanDefinition(GatewayProxyFactoryBean.class,
|
||||
() -> new GatewayProxyFactoryBean<>(serviceInterface));
|
||||
|
||||
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
|
||||
BeanDefinitionBuilder methodMetadataBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class, GatewayMethodMetadata::new);
|
||||
|
||||
if (hasDefaultPayloadExpression) {
|
||||
methodMetadataBuilder.addPropertyValue("payloadExpression",
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
|
||||
.addConstructorArgValue(defaultPayloadExpression)
|
||||
.getBeanDefinition());
|
||||
}
|
||||
|
||||
if (hasDefaultHeaders) {
|
||||
Map<String, Object> headerExpressions = new ManagedMap<>();
|
||||
for (Map<String, Object> header : defaultHeaders) {
|
||||
String headerValue = (String) header.get("value");
|
||||
String headerExpression = (String) header.get("expression");
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
|
||||
if (hasValue == StringUtils.hasText(headerExpression)) {
|
||||
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
|
||||
"is required on a gateway's header.");
|
||||
}
|
||||
|
||||
BeanDefinition expressionDef =
|
||||
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues()
|
||||
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
|
||||
|
||||
headerExpressions.put((String) header.get("name"), expressionDef);
|
||||
}
|
||||
methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions);
|
||||
}
|
||||
|
||||
gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition());
|
||||
}
|
||||
|
||||
|
||||
if (StringUtils.hasText(defaultRequestChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel);
|
||||
}
|
||||
if (StringUtils.hasText(defaultReplyChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("defaultReplyChannelName", defaultReplyChannel);
|
||||
}
|
||||
if (StringUtils.hasText(errorChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("errorChannelName", errorChannel);
|
||||
}
|
||||
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
|
||||
gatewayProxyBuilder.addPropertyValue("asyncExecutor", null);
|
||||
}
|
||||
else if (StringUtils.hasText(asyncExecutor)) {
|
||||
gatewayProxyBuilder.addPropertyReference("asyncExecutor", asyncExecutor);
|
||||
}
|
||||
if (StringUtils.hasText(mapper)) {
|
||||
gatewayProxyBuilder.addPropertyReference("mapper", mapper);
|
||||
}
|
||||
if (StringUtils.hasText(proxyDefaultMethods)) {
|
||||
gatewayProxyBuilder.addPropertyValue(PROXY_DEFAULT_METHODS_ATTR, proxyDefaultMethods);
|
||||
}
|
||||
|
||||
gatewayProxyBuilder.addPropertyValue("defaultRequestTimeoutExpressionString",
|
||||
gatewayAttributes.get("defaultRequestTimeout"));
|
||||
gatewayProxyBuilder.addPropertyValue("defaultReplyTimeoutExpressionString",
|
||||
gatewayAttributes.get("defaultReplyTimeout"));
|
||||
gatewayProxyBuilder.addPropertyValue("methodMetadataMap", gatewayAttributes.get("methods"));
|
||||
|
||||
|
||||
String id = (String) gatewayAttributes.get("name");
|
||||
if (!StringUtils.hasText(id)) {
|
||||
String serviceInterfaceName = serviceInterface.getName();
|
||||
id = Introspector.decapitalize(serviceInterfaceName.substring(serviceInterfaceName.lastIndexOf('.') + 1));
|
||||
}
|
||||
|
||||
gatewayProxyBuilder.addConstructorArgValue(serviceInterface);
|
||||
gatewayProxyBuilder.setPrimary(gatewayAttributes.containsKey(PRIMARY_ATTR));
|
||||
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) gatewayProxyBuilder.getBeanDefinition();
|
||||
beanDefinition.setTargetType(
|
||||
ResolvableType.forClassWithGenerics(GatewayProxyFactoryBean.class, serviceInterface));
|
||||
return new BeanDefinitionHolder(beanDefinition, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO until SPR-11710 will be resolved.
|
||||
* Captures the meta-annotation attribute values, in order.
|
||||
* @param importingClassMetadata The importing class metadata
|
||||
* @return The captured values.
|
||||
*/
|
||||
private static List<MultiValueMap<String, Object>> captureMetaAnnotationValues(
|
||||
AnnotationMetadata importingClassMetadata) {
|
||||
|
||||
Set<String> directAnnotations = importingClassMetadata.getAnnotationTypes();
|
||||
List<MultiValueMap<String, Object>> valuesHierarchy = new ArrayList<>();
|
||||
// Need to grab the values now; see SPR-11710
|
||||
for (String ann : directAnnotations) {
|
||||
Set<String> chain = importingClassMetadata.getMetaAnnotationTypes(ann);
|
||||
if (chain.contains(MessagingGateway.class.getName())) {
|
||||
for (String meta : chain) {
|
||||
MultiValueMap<String, Object> attributes = importingClassMetadata.getAllAnnotationAttributes(meta);
|
||||
if (attributes != null) {
|
||||
valuesHierarchy.add(attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return valuesHierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO until SPR-11709 will be resolved.
|
||||
* For any empty values, traverses back up the meta-annotation hierarchy to
|
||||
* see if a value has been overridden to empty, and replaces the first such value found.
|
||||
* @param valuesHierarchy The values hierarchy in order.
|
||||
* @param annotationAttributes The current attribute values.
|
||||
*/
|
||||
private static void replaceEmptyOverrides(List<MultiValueMap<String, Object>> valuesHierarchy,
|
||||
Map<String, Object> annotationAttributes) {
|
||||
|
||||
for (Entry<String, Object> entry : annotationAttributes.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (!MessagingAnnotationUtils.hasValue(value)) {
|
||||
// see if we overrode a value that was higher in the annotation chain
|
||||
for (MultiValueMap<String, Object> metaAttributesMap : valuesHierarchy) {
|
||||
Object newValue = metaAttributesMap.getFirst(entry.getKey());
|
||||
if (MessagingAnnotationUtils.hasValue(newValue)) {
|
||||
annotationAttributes.put(entry.getKey(), newValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigurableBeanFactory obtainBeanFactory(BeanDefinitionRegistry registry) {
|
||||
if (registry instanceof ConfigurableBeanFactory) {
|
||||
return (ConfigurableBeanFactory) registry;
|
||||
}
|
||||
else if (registry instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) registry).getBeanFactory();
|
||||
}
|
||||
throw new IllegalArgumentException("The provided 'BeanDefinitionRegistry' must be an instance " +
|
||||
"of 'ConfigurableBeanFactory' or 'ConfigurableApplicationContext', but given is: "
|
||||
+ registry.getClass());
|
||||
}
|
||||
|
||||
private static Class<?> getServiceInterface(String serviceInterface, ConfigurableBeanFactory beanFactory) {
|
||||
String actualServiceInterface = beanFactory.resolveEmbeddedValue(serviceInterface);
|
||||
if (!StringUtils.hasText(actualServiceInterface)) {
|
||||
return RequestReplyExchanger.class;
|
||||
}
|
||||
try {
|
||||
return ClassUtils.forName(actualServiceInterface, beanFactory.getBeanClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new BeanDefinitionStoreException("Cannot parse class for service interface", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -23,19 +23,33 @@ import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.config.MessagingGatewayRegistrar;
|
||||
import org.springframework.integration.config.IntegrationConfigUtils;
|
||||
import org.springframework.integration.gateway.GatewayMethodMetadata;
|
||||
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
|
||||
import org.springframework.integration.gateway.RequestReplyExchanger;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
||||
@@ -49,7 +63,7 @@ import org.springframework.util.xml.DomUtils;
|
||||
*/
|
||||
public class GatewayParser implements BeanDefinitionParser {
|
||||
|
||||
private final MessagingGatewayRegistrar registrar = new MessagingGatewayRegistrar();
|
||||
private static final String PROXY_DEFAULT_METHODS_ATTR = "proxyDefaultMethods";
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(final Element element, ParserContext parserContext) {
|
||||
@@ -88,7 +102,7 @@ public class GatewayParser implements BeanDefinitionParser {
|
||||
|
||||
gatewayAttributes.put("proxyDefaultMethods", element.getAttribute("proxy-default-methods"));
|
||||
|
||||
BeanDefinitionHolder gatewayHolder = this.registrar.parse(gatewayAttributes, parserContext.getRegistry());
|
||||
BeanDefinitionHolder gatewayHolder = buildBeanDefinition(gatewayAttributes, parserContext);
|
||||
if (isNested) {
|
||||
return gatewayHolder.getBeanDefinition();
|
||||
}
|
||||
@@ -98,7 +112,7 @@ public class GatewayParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void headers(Element element, Map<String, Object> gatewayAttributes) {
|
||||
private static void headers(Element element, Map<String, Object> gatewayAttributes) {
|
||||
List<Element> headerElements = DomUtils.getChildElementsByTagName(element, "default-header");
|
||||
if (!CollectionUtils.isEmpty(headerElements)) {
|
||||
List<Map<String, Object>> headers = new ArrayList<>(headerElements.size());
|
||||
@@ -114,8 +128,9 @@ public class GatewayParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void methods(final Element element, ParserContext parserContext,
|
||||
private static void methods(Element element, ParserContext parserContext,
|
||||
final Map<String, Object> gatewayAttributes) {
|
||||
|
||||
List<Element> methodElements = DomUtils.getChildElementsByTagName(element, "method");
|
||||
if (!CollectionUtils.isEmpty(methodElements)) {
|
||||
Map<String, BeanDefinition> methodMetadataMap = new ManagedMap<>();
|
||||
@@ -163,4 +178,146 @@ public class GatewayParser implements BeanDefinitionParser {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static BeanDefinitionHolder buildBeanDefinition(Map<String, Object> gatewayAttributes, // NOSONAR complexity
|
||||
ParserContext parserContext) {
|
||||
|
||||
BeanDefinitionRegistry registry = parserContext.getRegistry();
|
||||
String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object>[] defaultHeaders = (Map<String, Object>[]) gatewayAttributes.get("defaultHeaders");
|
||||
|
||||
String defaultRequestChannel = (String) gatewayAttributes.get("defaultRequestChannel");
|
||||
String defaultReplyChannel = (String) gatewayAttributes.get("defaultReplyChannel");
|
||||
String errorChannel = (String) gatewayAttributes.get("errorChannel");
|
||||
String asyncExecutor = (String) gatewayAttributes.get("asyncExecutor");
|
||||
String mapper = (String) gatewayAttributes.get("mapper");
|
||||
String proxyDefaultMethods = (String) gatewayAttributes.get(PROXY_DEFAULT_METHODS_ATTR);
|
||||
|
||||
boolean hasMapper = StringUtils.hasText(mapper);
|
||||
boolean hasDefaultPayloadExpression = StringUtils.hasText(defaultPayloadExpression);
|
||||
Assert.state(!hasMapper || !hasDefaultPayloadExpression,
|
||||
"'defaultPayloadExpression' is not allowed when a 'mapper' is provided");
|
||||
|
||||
boolean hasDefaultHeaders = !ObjectUtils.isEmpty(defaultHeaders);
|
||||
Assert.state(!hasMapper || !hasDefaultHeaders,
|
||||
"'defaultHeaders' are not allowed when a 'mapper' is provided");
|
||||
|
||||
ConfigurableBeanFactory beanFactory = obtainBeanFactory(registry);
|
||||
Class<?> serviceInterface = getServiceInterface((String) gatewayAttributes.get("serviceInterface"), beanFactory);
|
||||
|
||||
BeanDefinitionBuilder gatewayProxyBuilder =
|
||||
BeanDefinitionBuilder.rootBeanDefinition(GatewayProxyFactoryBean.class)
|
||||
.addConstructorArgValue(serviceInterface);
|
||||
|
||||
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
|
||||
BeanDefinition methodMetadata = getMethodMetadataBeanDefinition(defaultPayloadExpression, defaultHeaders);
|
||||
|
||||
gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadata);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(defaultRequestChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel);
|
||||
}
|
||||
if (StringUtils.hasText(defaultReplyChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("defaultReplyChannelName", defaultReplyChannel);
|
||||
}
|
||||
if (StringUtils.hasText(errorChannel)) {
|
||||
gatewayProxyBuilder.addPropertyValue("errorChannelName", errorChannel);
|
||||
}
|
||||
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
|
||||
gatewayProxyBuilder.addPropertyValue("asyncExecutor", null);
|
||||
}
|
||||
else if (StringUtils.hasText(asyncExecutor)) {
|
||||
gatewayProxyBuilder.addPropertyReference("asyncExecutor", asyncExecutor);
|
||||
}
|
||||
if (StringUtils.hasText(mapper)) {
|
||||
gatewayProxyBuilder.addPropertyReference("mapper", mapper);
|
||||
}
|
||||
if (StringUtils.hasText(proxyDefaultMethods)) {
|
||||
gatewayProxyBuilder.addPropertyValue(PROXY_DEFAULT_METHODS_ATTR, proxyDefaultMethods);
|
||||
}
|
||||
|
||||
gatewayProxyBuilder.addPropertyValue("defaultRequestTimeoutExpressionString",
|
||||
gatewayAttributes.get("defaultRequestTimeout"));
|
||||
gatewayProxyBuilder.addPropertyValue("defaultReplyTimeoutExpressionString",
|
||||
gatewayAttributes.get("defaultReplyTimeout"));
|
||||
gatewayProxyBuilder.addPropertyValue("methodMetadataMap", gatewayAttributes.get("methods"));
|
||||
|
||||
String id = (String) gatewayAttributes.get("name");
|
||||
if (!StringUtils.hasText(id)) {
|
||||
BeanNameGenerator beanNameGenerator =
|
||||
IntegrationConfigUtils.annotationBeanNameGenerator(registry);
|
||||
id = beanNameGenerator.generateBeanName(new AnnotatedGenericBeanDefinition(serviceInterface), registry);
|
||||
}
|
||||
|
||||
RootBeanDefinition beanDefinition = (RootBeanDefinition) gatewayProxyBuilder.getBeanDefinition();
|
||||
beanDefinition.setTargetType(
|
||||
ResolvableType.forClassWithGenerics(GatewayProxyFactoryBean.class, serviceInterface));
|
||||
return new BeanDefinitionHolder(beanDefinition, id);
|
||||
}
|
||||
|
||||
private static BeanDefinition getMethodMetadataBeanDefinition(String defaultPayloadExpression,
|
||||
Map<String, Object>[] defaultHeaders) {
|
||||
|
||||
BeanDefinitionBuilder methodMetadataBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class, GatewayMethodMetadata::new);
|
||||
|
||||
if (StringUtils.hasText(defaultPayloadExpression)) {
|
||||
methodMetadataBuilder.addPropertyValue("payloadExpression",
|
||||
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
|
||||
.addConstructorArgValue(defaultPayloadExpression)
|
||||
.getBeanDefinition());
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(defaultHeaders)) {
|
||||
Map<String, Object> headerExpressions = new ManagedMap<>();
|
||||
for (Map<String, Object> header : defaultHeaders) {
|
||||
String headerValue = (String) header.get("value");
|
||||
String headerExpression = (String) header.get("expression");
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
|
||||
if (hasValue == StringUtils.hasText(headerExpression)) {
|
||||
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
|
||||
"is required on a gateway's header.");
|
||||
}
|
||||
|
||||
BeanDefinition expressionDef =
|
||||
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
|
||||
expressionDef.getConstructorArgumentValues()
|
||||
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
|
||||
|
||||
headerExpressions.put((String) header.get("name"), expressionDef);
|
||||
}
|
||||
methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions);
|
||||
}
|
||||
return methodMetadataBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private static ConfigurableBeanFactory obtainBeanFactory(BeanDefinitionRegistry registry) {
|
||||
if (registry instanceof ConfigurableBeanFactory) {
|
||||
return (ConfigurableBeanFactory) registry;
|
||||
}
|
||||
else if (registry instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) registry).getBeanFactory();
|
||||
}
|
||||
throw new IllegalArgumentException("The provided 'BeanDefinitionRegistry' must be an instance " +
|
||||
"of 'ConfigurableBeanFactory' or 'ConfigurableApplicationContext', but given is: "
|
||||
+ registry.getClass());
|
||||
}
|
||||
|
||||
private static Class<?> getServiceInterface(String serviceInterface, ConfigurableBeanFactory beanFactory) {
|
||||
String actualServiceInterface = beanFactory.resolveEmbeddedValue(serviceInterface);
|
||||
if (!StringUtils.hasText(actualServiceInterface)) {
|
||||
return RequestReplyExchanger.class;
|
||||
}
|
||||
try {
|
||||
return ClassUtils.forName(actualServiceInterface, beanFactory.getBeanClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new BeanDefinitionStoreException("Cannot parse class for service interface", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,21 +16,29 @@
|
||||
|
||||
package org.springframework.integration.gateway;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.expression.StandardBeanExpressionResolver;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.JavaUtils;
|
||||
import org.springframework.integration.annotation.AnnotationConstants;
|
||||
import org.springframework.integration.annotation.GatewayHeader;
|
||||
import org.springframework.integration.annotation.MessagingGateway;
|
||||
import org.springframework.integration.util.MessagingAnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -52,32 +60,55 @@ public class AnnotationGatewayProxyFactoryBean<T> extends GatewayProxyFactoryBea
|
||||
|
||||
private final AnnotationAttributes gatewayAttributes;
|
||||
|
||||
private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
|
||||
|
||||
private BeanExpressionContext expressionContext;
|
||||
|
||||
public AnnotationGatewayProxyFactoryBean(Class<T> serviceInterface) {
|
||||
super(serviceInterface);
|
||||
this.gatewayAttributes = mergeAnnotationAttributes(serviceInterface);
|
||||
}
|
||||
|
||||
private static AnnotationAttributes mergeAnnotationAttributes(Class<?> serviceInterface) {
|
||||
AnnotationAttributes annotationAttributes =
|
||||
AnnotatedElementUtils.getMergedAnnotationAttributes(serviceInterface,
|
||||
MessagingGateway.class.getName(), false, true);
|
||||
if (annotationAttributes == null) {
|
||||
annotationAttributes = AnnotationUtils.getAnnotationAttributes(
|
||||
AnnotationUtils.synthesizeAnnotation(MessagingGateway.class), false, true);
|
||||
AnnotationUtils.getAnnotationAttributes(null,
|
||||
AnnotationUtils.synthesizeAnnotation(MessagingGateway.class));
|
||||
|
||||
if (AnnotatedElementUtils.isAnnotated(serviceInterface, MessagingGateway.class)) {
|
||||
Annotation annotation =
|
||||
MergedAnnotations.from(serviceInterface)
|
||||
.get(MessagingGateway.class)
|
||||
.getRoot()
|
||||
.synthesize();
|
||||
|
||||
List<Annotation> annotationChain =
|
||||
MessagingAnnotationUtils.getAnnotationChain(annotation, MessagingGateway.class);
|
||||
|
||||
for (Map.Entry<String, Object> attribute : annotationAttributes.entrySet()) {
|
||||
String key = attribute.getKey();
|
||||
Object value = MessagingAnnotationUtils.resolveAttribute(annotationChain, key, Object.class);
|
||||
if (value != null) {
|
||||
attribute.setValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.gatewayAttributes = annotationAttributes;
|
||||
|
||||
String id = annotationAttributes.getString("name");
|
||||
if (StringUtils.hasText(id)) {
|
||||
setBeanName(id);
|
||||
}
|
||||
return annotationAttributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
if (getBeanFactory() instanceof ConfigurableBeanFactory beanFactory) {
|
||||
this.resolver = beanFactory.getBeanExpressionResolver();
|
||||
this.expressionContext = new BeanExpressionContext(beanFactory, null);
|
||||
}
|
||||
|
||||
populateGatewayMethodMetadataIfAny();
|
||||
|
||||
String defaultRequestTimeout = resolveAttribute("defaultRequestTimeout");
|
||||
String defaultReplyTimeout = resolveAttribute("defaultReplyTimeout");
|
||||
|
||||
|
||||
JavaUtils.INSTANCE
|
||||
.acceptIfCondition(getDefaultRequestChannel() == null && getDefaultRequestChannelName() == null,
|
||||
resolveAttribute("defaultRequestChannel"),
|
||||
@@ -89,11 +120,11 @@ public class AnnotationGatewayProxyFactoryBean<T> extends GatewayProxyFactoryBea
|
||||
resolveAttribute("errorChannel"),
|
||||
this::setErrorChannelName)
|
||||
.acceptIfCondition(getDefaultRequestTimeout() == null && StringUtils.hasText(defaultRequestTimeout),
|
||||
defaultRequestTimeout,
|
||||
value -> setDefaultRequestTimeout(Long.parseLong(value)))
|
||||
evaluateExpression(defaultRequestTimeout, Long.class),
|
||||
this::setDefaultRequestTimeout)
|
||||
.acceptIfCondition(getDefaultReplyTimeout() == null && StringUtils.hasText(defaultReplyTimeout),
|
||||
defaultReplyTimeout,
|
||||
value -> setDefaultReplyTimeout(Long.parseLong(value)));
|
||||
evaluateExpression(defaultReplyTimeout, Long.class),
|
||||
this::setDefaultReplyTimeout);
|
||||
|
||||
populateAsyncExecutorIfAny();
|
||||
|
||||
@@ -109,12 +140,11 @@ public class AnnotationGatewayProxyFactoryBean<T> extends GatewayProxyFactoryBea
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();
|
||||
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) getBeanFactory();
|
||||
|
||||
String defaultPayloadExpression = resolveAttribute("defaultPayloadExpression");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object>[] defaultHeaders = (Map<String, Object>[]) this.gatewayAttributes.get("defaultHeaders");
|
||||
GatewayHeader[] defaultHeaders = (GatewayHeader[]) this.gatewayAttributes.get("defaultHeaders");
|
||||
|
||||
String mapper = resolveAttribute("mapper");
|
||||
|
||||
@@ -141,14 +171,14 @@ public class AnnotationGatewayProxyFactoryBean<T> extends GatewayProxyFactoryBea
|
||||
Map<String, Expression> headerExpressions =
|
||||
Arrays.stream(defaultHeaders)
|
||||
.collect(Collectors.toMap(
|
||||
header -> beanFactory.resolveEmbeddedValue((String) header.get("name")),
|
||||
header -> beanFactory.resolveEmbeddedValue(header.name()),
|
||||
header -> {
|
||||
String headerValue =
|
||||
beanFactory.resolveEmbeddedValue((String) header.get("value"));
|
||||
beanFactory.resolveEmbeddedValue(header.value());
|
||||
boolean hasValue = StringUtils.hasText(headerValue);
|
||||
|
||||
String headerExpression =
|
||||
beanFactory.resolveEmbeddedValue((String) header.get("expression"));
|
||||
beanFactory.resolveEmbeddedValue(header.expression());
|
||||
|
||||
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
|
||||
"exactly one of 'value' or 'expression' is required on a gateway's " +
|
||||
@@ -180,8 +210,19 @@ public class AnnotationGatewayProxyFactoryBean<T> extends GatewayProxyFactoryBea
|
||||
|
||||
@Nullable
|
||||
private String resolveAttribute(String attributeName) {
|
||||
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();
|
||||
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) getBeanFactory();
|
||||
return beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString(attributeName));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <V> V evaluateExpression(@Nullable String value, Class<V> targetClass) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
Object result = this.resolver.evaluate(value, this.expressionContext);
|
||||
return getConversionService().convert(result, targetClass);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.expression.EnvironmentAccessor;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.core.serializer.support.SerializingConverter;
|
||||
@@ -488,6 +489,10 @@ public class EnableIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testMessagingGateway() throws InterruptedException {
|
||||
String gatewayBeanName = AnnotatedElementUtils.findMergedAnnotation(TestGateway.class, Component.class).value();
|
||||
assertThat(gatewayBeanName).isEqualTo("namedTestGateway");
|
||||
assertThat(this.testGateway).isSameAs(this.context.getBean(gatewayBeanName));
|
||||
|
||||
String payload = "bar";
|
||||
String result = this.testGateway.echo(payload);
|
||||
assertThat(result.substring(0, payload.length())).isEqualTo(payload.toUpperCase());
|
||||
@@ -1464,7 +1469,7 @@ public class EnableIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@TestMessagingGateway
|
||||
@TestMessagingGateway("namedTestGateway")
|
||||
public interface TestGateway {
|
||||
|
||||
@Gateway(headers = @GatewayHeader(name = "calledMethod", expression = "method.name"))
|
||||
@@ -1494,6 +1499,9 @@ public class EnableIntegrationTests {
|
||||
defaultHeaders = @GatewayHeader(name = "foo", value = "FOO"))
|
||||
public @interface TestMessagingGateway {
|
||||
|
||||
@AliasFor(annotation = MessagingGateway.class, attribute = "value")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(annotation = MessagingGateway.class, attribute = "defaultRequestChannel")
|
||||
String defaultRequestChannel() default "";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
* Copyright 2016-2022 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.
|
||||
@@ -21,21 +21,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.integration.annotation.IntegrationComponentScan;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.IntegrationComponentScanRegistrar;
|
||||
import org.springframework.integration.dsl.flows.IntegrationFlowTests;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class EnableComponentScanTests {
|
||||
|
||||
@@ -68,13 +68,15 @@ public class EnableComponentScanTests {
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
super.registerBeanDefinitions(
|
||||
AnnotationMetadata.introspect(IntegrationComponentScanConfiguration.class), registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<String> getBasePackages(AnnotationMetadata importingClassMetadata,
|
||||
protected Collection<String> getBasePackages(AnnotationAttributes importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
return Collections.singleton(ClassUtils.getPackageName(IntegrationFlowTests.ControlBusGateway.class));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,10 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
@@ -94,6 +96,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -121,11 +124,11 @@ public class GatewayInterfaceTests {
|
||||
private NoExecGateway noExecGateway;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("&gatewayInterfaceTests$ExecGateway")
|
||||
@Qualifier("&execGateway")
|
||||
private GatewayProxyFactoryBean<?> execGatewayFB;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("&gatewayInterfaceTests$NoExecGateway")
|
||||
@Qualifier("&noExecutorGateway")
|
||||
private GatewayProxyFactoryBean<?> noExecGatewayFB;
|
||||
|
||||
@Autowired
|
||||
@@ -642,8 +645,10 @@ public class GatewayInterfaceTests {
|
||||
@Configuration
|
||||
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
|
||||
classes = AutoCreateChannelService.class))
|
||||
@IntegrationComponentScan(useDefaultFilters = false,
|
||||
includeFilters = @ComponentScan.Filter(TestMessagingGateway.class))
|
||||
@IntegrationComponentScan(
|
||||
useDefaultFilters = false,
|
||||
includeFilters = @ComponentScan.Filter(TestMessagingGateway.class),
|
||||
nameGenerator = CustomBeanNameGenerator.class)
|
||||
@EnableIntegration
|
||||
@Import(ImportedGateway.class)
|
||||
public static class TestConfig {
|
||||
@@ -754,7 +759,7 @@ public class GatewayInterfaceTests {
|
||||
|
||||
}
|
||||
|
||||
@MessagingGateway(asyncExecutor = AnnotationConstants.NULL)
|
||||
@MessagingGateway(name = "noExecutorGateway", asyncExecutor = AnnotationConstants.NULL)
|
||||
@TestMessagingGateway
|
||||
public interface NoExecGateway {
|
||||
|
||||
@@ -836,4 +841,20 @@ public class GatewayInterfaceTests {
|
||||
|
||||
}
|
||||
|
||||
public static class CustomBeanNameGenerator extends AnnotationBeanNameGenerator {
|
||||
|
||||
@Override
|
||||
protected String buildDefaultBeanName(BeanDefinition definition) {
|
||||
try {
|
||||
Class<?> beanClass = ClassUtils.forName(definition.getBeanClassName(),
|
||||
ClassUtils.getDefaultClassLoader());
|
||||
return ClassUtils.getShortNameAsProperty(beanClass);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -322,6 +322,10 @@ Starting with version 6.0, an interface with the `@MessagingGateway` can also be
|
||||
Starting with version 6.0, `@MessagingGateway` interfaces can be used in the standard Spring `@Import` configuration.
|
||||
This may be used as an alternative to the `@IntegrationComponentScan` or manual `AnnotationGatewayProxyFactoryBean` bean definitions.
|
||||
|
||||
The `@MessagingGateway` is meta-annotated with a `@MessageEndpoint` since version `6.0` and the `name()` attribute is, essentially, aliased to the `@Compnent.value()`.
|
||||
This way the bean names generating strategy for gateway proxies is realigned with the standard Spring annotation configuration for scanned and imported components.
|
||||
The default `AnnotationBeanNameGenerator` can be overridden globally via an `AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR` or as a `@IntegrationComponentScan.nameGenerator()` attribute.
|
||||
|
||||
NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration` class.
|
||||
See <<./overview.adoc#configuration-enable-integration,Configuration and `@EnableIntegration`>> for more information.
|
||||
|
||||
|
||||
@@ -114,7 +114,10 @@ The Messaging Gateway interface method can now return `Future<Void>` and `Mono<V
|
||||
|
||||
Alongside with a `@MessagingGateway` annotation the interface can also be marked with a `@Primary`.
|
||||
|
||||
`@MessagingGateway` interfaces can now be `@Import`ed into a configuration.
|
||||
`@MessagingGateway` interfaces can now be use as an `@Import` resources for configuration.
|
||||
|
||||
The default naming strategy for gateway proxy beans can be customized via `@IntegrationComponentScan.nameGenerator()` attribute.
|
||||
If `AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR` bean is present, it is consulted otherwise before falling back to the `AnnotationBeanNameGenerator`.
|
||||
|
||||
See <<./gateway.adoc#gateway, Messaging Gateway>> for more information.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user