Skip to content

Commit a40a826

Browse files
committed
GH-3926: BeanNameGenerator for @MessagingGateway
Fixes #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`
1 parent e52e352 commit a40a826

File tree

9 files changed

+159
-87
lines changed

9 files changed

+159
-87
lines changed

spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.lang.annotation.RetentionPolicy;
2323
import java.lang.annotation.Target;
2424

25+
import org.springframework.beans.factory.support.BeanNameGenerator;
2526
import org.springframework.context.annotation.ComponentScan.Filter;
2627
import org.springframework.context.annotation.Import;
2728
import org.springframework.core.annotation.AliasFor;
@@ -105,4 +106,17 @@
105106
*/
106107
Filter[] excludeFilters() default { };
107108

109+
110+
/**
111+
* The {@link BeanNameGenerator} class to be used for naming detected Spring Integration components.
112+
* <p>The default value of the {@link BeanNameGenerator} interface itself indicates
113+
* that the scanner used to process this {@code @IntegrationComponentScan} annotation should
114+
* use its inherited bean name generator, e.g. the default
115+
* {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}
116+
* or any custom instance supplied to the application context at bootstrap time.
117+
* @since 6.0
118+
* @see org.springframework.context.annotation.ComponentScan#nameGenerator()
119+
*/
120+
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
121+
108122
}

spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.springframework.beans.factory.config.BeanDefinition;
3737
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3838
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
39+
import org.springframework.beans.factory.support.BeanNameGenerator;
3940
import org.springframework.context.EnvironmentAware;
4041
import org.springframework.context.ResourceLoaderAware;
4142
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
@@ -90,10 +91,11 @@ public void setEnvironment(Environment environment) {
9091

9192
@Override
9293
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
93-
Map<String, Object> componentScan =
94-
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName());
94+
AnnotationAttributes componentScan =
95+
AnnotationAttributes.fromMap(
96+
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName()));
9597

96-
Collection<String> basePackages = getBasePackages(importingClassMetadata, registry);
98+
Collection<String> basePackages = getBasePackages(componentScan, registry);
9799

98100
if (basePackages.isEmpty()) {
99101
basePackages = Collections.singleton(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
@@ -119,55 +121,59 @@ protected BeanDefinitionRegistry getRegistry() {
119121

120122
scanner.setResourceLoader(this.resourceLoader);
121123

124+
BeanNameGenerator beanNameGenerator = IntegrationConfigUtils.annotationBeanNameGenerator(registry);
125+
126+
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
127+
if (!(BeanNameGenerator.class == generatorClass)) {
128+
beanNameGenerator = BeanUtils.instantiateClass(generatorClass);
129+
}
130+
122131
for (String basePackage : basePackages) {
123132
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
124133
for (BeanDefinition candidateComponent : candidateComponents) {
125134
if (candidateComponent instanceof AnnotatedBeanDefinition) {
126135
for (ImportBeanDefinitionRegistrar registrar : this.componentRegistrars.values()) {
127136
registrar.registerBeanDefinitions(((AnnotatedBeanDefinition) candidateComponent).getMetadata(),
128-
registry);
137+
registry, beanNameGenerator);
129138
}
130139
}
131140
}
132141
}
133142
}
134143

135-
private void filter(BeanDefinitionRegistry registry, Map<String, Object> componentScan,
144+
private void filter(BeanDefinitionRegistry registry, AnnotationAttributes componentScan,
136145
ClassPathScanningCandidateComponentProvider scanner) {
137146

138-
if ((boolean) componentScan.get("useDefaultFilters")) { // NOSONAR - never null
147+
if (componentScan.getBoolean("useDefaultFilters")) { // NOSONAR - never null
139148
for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {
140149
scanner.addIncludeFilter(typeFilter);
141150
}
142151
}
143152

144-
for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("includeFilters")) {
153+
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
145154
for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
146155
scanner.addIncludeFilter(typeFilter);
147156
}
148157
}
149-
for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("excludeFilters")) {
158+
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
150159
for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
151160
scanner.addExcludeFilter(typeFilter);
152161
}
153162
}
154163
}
155164

156-
protected Collection<String> getBasePackages(AnnotationMetadata importingClassMetadata,
165+
protected Collection<String> getBasePackages(AnnotationAttributes componentScan,
157166
@SuppressWarnings("unused") BeanDefinitionRegistry registry) {
158167

159-
Map<String, Object> componentScan =
160-
importingClassMetadata.getAnnotationAttributes(IntegrationComponentScan.class.getName());
161-
162168
Set<String> basePackages = new HashSet<>();
163169

164-
for (String pkg : (String[]) componentScan.get("value")) { // NOSONAR - never null
170+
for (String pkg : componentScan.getStringArray("value")) {
165171
if (StringUtils.hasText(pkg)) {
166172
basePackages.add(pkg);
167173
}
168174
}
169175

170-
for (Class<?> clazz : (Class<?>[]) componentScan.get("basePackageClasses")) {
176+
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
171177
basePackages.add(ClassUtils.getPackageName(clazz));
172178
}
173179

@@ -180,38 +186,33 @@ private List<TypeFilter> typeFiltersFor(AnnotationAttributes filter, BeanDefinit
180186

181187
for (Class<?> filterClass : filter.getClassArray("classes")) {
182188
switch (filterType) {
183-
case ANNOTATION:
189+
case ANNOTATION -> {
184190
Assert.isAssignable(Annotation.class, filterClass,
185191
"An error occurred while processing a @IntegrationComponentScan ANNOTATION type filter: ");
186192
@SuppressWarnings("unchecked")
187193
Class<Annotation> annotationType = (Class<Annotation>) filterClass;
188194
typeFilters.add(new AnnotationTypeFilter(annotationType));
189-
break;
190-
case ASSIGNABLE_TYPE:
191-
typeFilters.add(new AssignableTypeFilter(filterClass));
192-
break;
193-
case CUSTOM:
195+
}
196+
case ASSIGNABLE_TYPE -> typeFilters.add(new AssignableTypeFilter(filterClass));
197+
case CUSTOM -> {
194198
Assert.isAssignable(TypeFilter.class, filterClass,
195199
"An error occurred while processing a @IntegrationComponentScan CUSTOM type filter: ");
196200
TypeFilter typeFilter = BeanUtils.instantiateClass(filterClass, TypeFilter.class);
197201
invokeAwareMethods(filter, this.environment, this.resourceLoader, registry);
198202
typeFilters.add(typeFilter);
199-
break;
200-
default:
201-
throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
203+
}
204+
default -> throw new IllegalArgumentException("Filter type not supported with Class value: " +
205+
filterType);
202206
}
203207
}
204208

205209
for (String expression : filter.getStringArray("pattern")) {
206210
switch (filterType) {
207-
case ASPECTJ:
208-
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
209-
break;
210-
case REGEX:
211-
typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
212-
break;
213-
default:
214-
throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType);
211+
case ASPECTJ ->
212+
typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
213+
case REGEX -> typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
214+
default -> throw new IllegalArgumentException("Filter type not supported with String pattern: "
215+
+ filterType);
215216
}
216217
}
217218

spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationConfigUtils.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616

1717
package org.springframework.integration.config;
1818

19+
import org.springframework.beans.factory.config.SingletonBeanRegistry;
1920
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2021
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
22+
import org.springframework.beans.factory.support.BeanNameGenerator;
2123
import org.springframework.beans.factory.support.RootBeanDefinition;
24+
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
25+
import org.springframework.context.annotation.AnnotationConfigUtils;
2226
import org.springframework.integration.channel.DirectChannel;
2327

2428
/**
@@ -64,6 +68,20 @@ public static void autoCreateDirectChannel(String channelName, BeanDefinitionReg
6468
registry.registerBeanDefinition(channelName, new RootBeanDefinition(DirectChannel.class, DirectChannel::new));
6569
}
6670

71+
public static BeanNameGenerator annotationBeanNameGenerator(BeanDefinitionRegistry registry) {
72+
BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
73+
if (registry instanceof SingletonBeanRegistry singletonBeanRegistry) {
74+
BeanNameGenerator generator =
75+
(BeanNameGenerator) singletonBeanRegistry.getSingleton(
76+
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
77+
if (generator != null) {
78+
beanNameGenerator = generator;
79+
}
80+
}
81+
82+
return beanNameGenerator;
83+
}
84+
6785
private IntegrationConfigUtils() {
6886
}
6987

spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
package org.springframework.integration.config;
1818

19-
import java.beans.Introspector;
2019
import java.util.ArrayList;
2120
import java.util.List;
2221
import java.util.Map;
2322
import java.util.Map.Entry;
2423
import java.util.Set;
2524

2625
import org.springframework.beans.factory.BeanDefinitionStoreException;
26+
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
2727
import org.springframework.beans.factory.config.BeanDefinition;
2828
import org.springframework.beans.factory.config.BeanDefinitionHolder;
2929
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3030
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3131
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
3232
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
33+
import org.springframework.beans.factory.support.BeanNameGenerator;
3334
import org.springframework.beans.factory.support.ManagedMap;
3435
import org.springframework.beans.factory.support.RootBeanDefinition;
3536
import org.springframework.context.ConfigurableApplicationContext;
@@ -67,8 +68,10 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar
6768
private static final String PRIMARY_ATTR = "primary";
6869

6970
@Override
70-
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
71-
if (importingClassMetadata != null && importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
71+
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
72+
BeanNameGenerator beanNameGenerator) {
73+
74+
if (importingClassMetadata.isAnnotated(MessagingGateway.class.getName())) {
7275
Assert.isTrue(importingClassMetadata.isInterface(),
7376
"@MessagingGateway can only be specified on an interface");
7477
List<MultiValueMap<String, Object>> valuesHierarchy = captureMetaAnnotationValues(importingClassMetadata);
@@ -81,11 +84,14 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
8184
if (importingClassMetadata.isAnnotated(Primary.class.getName())) {
8285
annotationAttributes.put(PRIMARY_ATTR, true);
8386
}
84-
BeanDefinitionReaderUtils.registerBeanDefinition(parse(annotationAttributes, registry), registry);
87+
BeanDefinitionHolder definitionHolder = parse(annotationAttributes, registry, beanNameGenerator);
88+
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
8589
}
8690
}
8791

88-
public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes, BeanDefinitionRegistry registry) { // NOSONAR complexity
92+
public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes, // NOSONAR complexity
93+
BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) {
94+
8995
String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression");
9096

9197
@SuppressWarnings("unchecked")
@@ -115,42 +121,11 @@ public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes, BeanDef
115121
() -> new GatewayProxyFactoryBean<>(serviceInterface));
116122

117123
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
118-
BeanDefinitionBuilder methodMetadataBuilder =
119-
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class, GatewayMethodMetadata::new);
120-
121-
if (hasDefaultPayloadExpression) {
122-
methodMetadataBuilder.addPropertyValue("payloadExpression",
123-
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
124-
.addConstructorArgValue(defaultPayloadExpression)
125-
.getBeanDefinition());
126-
}
127-
128-
if (hasDefaultHeaders) {
129-
Map<String, Object> headerExpressions = new ManagedMap<>();
130-
for (Map<String, Object> header : defaultHeaders) {
131-
String headerValue = (String) header.get("value");
132-
String headerExpression = (String) header.get("expression");
133-
boolean hasValue = StringUtils.hasText(headerValue);
134-
135-
if (hasValue == StringUtils.hasText(headerExpression)) {
136-
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
137-
"is required on a gateway's header.");
138-
}
124+
BeanDefinition methodMetadata = getMethodMetadataBeanDefinition(defaultPayloadExpression, defaultHeaders);
139125

140-
BeanDefinition expressionDef =
141-
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
142-
expressionDef.getConstructorArgumentValues()
143-
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
144-
145-
headerExpressions.put((String) header.get("name"), expressionDef);
146-
}
147-
methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions);
148-
}
149-
150-
gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition());
126+
gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadata);
151127
}
152128

153-
154129
if (StringUtils.hasText(defaultRequestChannel)) {
155130
gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel);
156131
}
@@ -179,11 +154,9 @@ else if (StringUtils.hasText(asyncExecutor)) {
179154
gatewayAttributes.get("defaultReplyTimeout"));
180155
gatewayProxyBuilder.addPropertyValue("methodMetadataMap", gatewayAttributes.get("methods"));
181156

182-
183157
String id = (String) gatewayAttributes.get("name");
184158
if (!StringUtils.hasText(id)) {
185-
String serviceInterfaceName = serviceInterface.getName();
186-
id = Introspector.decapitalize(serviceInterfaceName.substring(serviceInterfaceName.lastIndexOf('.') + 1));
159+
id = beanNameGenerator.generateBeanName(new AnnotatedGenericBeanDefinition(serviceInterface), registry);
187160
}
188161

189162
gatewayProxyBuilder.addConstructorArgValue(serviceInterface);
@@ -195,6 +168,43 @@ else if (StringUtils.hasText(asyncExecutor)) {
195168
return new BeanDefinitionHolder(beanDefinition, id);
196169
}
197170

171+
private static BeanDefinition getMethodMetadataBeanDefinition(String defaultPayloadExpression,
172+
Map<String, Object>[] defaultHeaders) {
173+
174+
BeanDefinitionBuilder methodMetadataBuilder =
175+
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class, GatewayMethodMetadata::new);
176+
177+
if (StringUtils.hasText(defaultPayloadExpression)) {
178+
methodMetadataBuilder.addPropertyValue("payloadExpression",
179+
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
180+
.addConstructorArgValue(defaultPayloadExpression)
181+
.getBeanDefinition());
182+
}
183+
184+
if (!ObjectUtils.isEmpty(defaultHeaders)) {
185+
Map<String, Object> headerExpressions = new ManagedMap<>();
186+
for (Map<String, Object> header : defaultHeaders) {
187+
String headerValue = (String) header.get("value");
188+
String headerExpression = (String) header.get("expression");
189+
boolean hasValue = StringUtils.hasText(headerValue);
190+
191+
if (hasValue == StringUtils.hasText(headerExpression)) {
192+
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
193+
"is required on a gateway's header.");
194+
}
195+
196+
BeanDefinition expressionDef =
197+
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
198+
expressionDef.getConstructorArgumentValues()
199+
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
200+
201+
headerExpressions.put((String) header.get("name"), expressionDef);
202+
}
203+
methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions);
204+
}
205+
return methodMetadataBuilder.getBeanDefinition();
206+
}
207+
198208
/**
199209
* TODO until SPR-11710 will be resolved.
200210
* Captures the meta-annotation attribute values, in order.

spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
import org.springframework.beans.factory.xml.BeanDefinitionParser;
3333
import org.springframework.beans.factory.xml.ParserContext;
3434
import org.springframework.integration.config.ExpressionFactoryBean;
35+
import org.springframework.integration.config.IntegrationConfigUtils;
3536
import org.springframework.integration.config.MessagingGatewayRegistrar;
3637
import org.springframework.integration.gateway.GatewayMethodMetadata;
3738
import org.springframework.util.Assert;
@@ -88,7 +89,8 @@ public BeanDefinition parse(final Element element, ParserContext parserContext)
8889

8990
gatewayAttributes.put("proxyDefaultMethods", element.getAttribute("proxy-default-methods"));
9091

91-
BeanDefinitionHolder gatewayHolder = this.registrar.parse(gatewayAttributes, parserContext.getRegistry());
92+
BeanDefinitionHolder gatewayHolder = this.registrar.parse(gatewayAttributes, parserContext.getRegistry(),
93+
IntegrationConfigUtils.annotationBeanNameGenerator(parserContext.getRegistry()));
9294
if (isNested) {
9395
return gatewayHolder.getBeanDefinition();
9496
}

0 commit comments

Comments
 (0)