Skip to content

Commit db4f120

Browse files
artembilangaryrussell
authored andcommitted
Functional gateway bean definitions
* Rework `MessagingGatewayRegistrar` to parse messaging gateway annotation an `<gateway>` XML using a supplier variant for bean definition. Such a feature is required by Spring Native - otherwise we would need to register reflection info for to many internal Spring Integration classes * Such a change should benefit from regular JDK perspective, too - we don't do reflection for this kind of bean registrations
1 parent 1ac1633 commit db4f120

File tree

3 files changed

+219
-111
lines changed

3 files changed

+219
-111
lines changed

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

Lines changed: 135 additions & 67 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-2021 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.
@@ -18,30 +18,39 @@
1818

1919
import java.beans.Introspector;
2020
import java.util.ArrayList;
21+
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Map.Entry;
2425
import java.util.Set;
26+
import java.util.concurrent.Executor;
27+
import java.util.stream.Collectors;
2528

2629
import org.springframework.beans.factory.BeanDefinitionStoreException;
2730
import org.springframework.beans.factory.FactoryBean;
2831
import org.springframework.beans.factory.config.BeanDefinition;
2932
import org.springframework.beans.factory.config.BeanDefinitionHolder;
33+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
34+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
3035
import org.springframework.beans.factory.support.AbstractBeanDefinition;
31-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3236
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
3337
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
34-
import org.springframework.beans.factory.support.ManagedMap;
3538
import org.springframework.beans.factory.support.RootBeanDefinition;
39+
import org.springframework.context.ConfigurableApplicationContext;
3640
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
3741
import org.springframework.core.type.AnnotationMetadata;
42+
import org.springframework.expression.Expression;
43+
import org.springframework.expression.ExpressionParser;
3844
import org.springframework.expression.common.LiteralExpression;
45+
import org.springframework.expression.spel.standard.SpelExpressionParser;
3946
import org.springframework.integration.annotation.AnnotationConstants;
4047
import org.springframework.integration.annotation.MessagingGateway;
4148
import org.springframework.integration.gateway.GatewayMethodMetadata;
4249
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
50+
import org.springframework.integration.gateway.MethodArgsMessageMapper;
4351
import org.springframework.integration.util.MessagingAnnotationUtils;
4452
import org.springframework.util.Assert;
53+
import org.springframework.util.ClassUtils;
4554
import org.springframework.util.MultiValueMap;
4655
import org.springframework.util.ObjectUtils;
4756
import org.springframework.util.StringUtils;
@@ -58,6 +67,8 @@
5867
*/
5968
public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar {
6069

70+
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
71+
6172
private static final String PROXY_DEFAULT_METHODS_ATTR = "proxyDefaultMethods";
6273

6374
@Override
@@ -72,11 +83,14 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
7283
annotationAttributes.put("serviceInterface", importingClassMetadata.getClassName());
7384
annotationAttributes.put(PROXY_DEFAULT_METHODS_ATTR,
7485
"" + annotationAttributes.remove(PROXY_DEFAULT_METHODS_ATTR));
75-
BeanDefinitionReaderUtils.registerBeanDefinition(parse(annotationAttributes), registry);
86+
BeanDefinitionReaderUtils.registerBeanDefinition(gatewayProxyBeanDefinition(annotationAttributes, registry),
87+
registry);
7688
}
7789
}
7890

79-
public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes) { // NOSONAR complexity
91+
public BeanDefinitionHolder gatewayProxyBeanDefinition(Map<String, Object> gatewayAttributes,
92+
BeanDefinitionRegistry registry) {
93+
8094
String defaultPayloadExpression = (String) gatewayAttributes.get("defaultPayloadExpression");
8195

8296
@SuppressWarnings("unchecked")
@@ -98,89 +112,143 @@ public BeanDefinitionHolder parse(Map<String, Object> gatewayAttributes) { // NO
98112
Assert.state(!hasMapper || !hasDefaultHeaders,
99113
"'defaultHeaders' are not allowed when a 'mapper' is provided");
100114

101-
BeanDefinitionBuilder gatewayProxyBuilder =
102-
BeanDefinitionBuilder.genericBeanDefinition(GatewayProxyFactoryBean.class);
103-
104-
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
105-
BeanDefinitionBuilder methodMetadataBuilder =
106-
BeanDefinitionBuilder.genericBeanDefinition(GatewayMethodMetadata.class);
115+
ConfigurableBeanFactory beanFactory = obtainBeanFactory(registry);
116+
EmbeddedValueResolver embeddedValueResolver = new EmbeddedValueResolver(beanFactory);
117+
Class<?> serviceInterface = getServiceInterface((String) gatewayAttributes.get("serviceInterface"), beanFactory);
107118

108-
if (hasDefaultPayloadExpression) {
109-
methodMetadataBuilder.addPropertyValue("payloadExpression",
110-
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
111-
.addConstructorArgValue(defaultPayloadExpression)
112-
.getBeanDefinition());
113-
}
119+
@SuppressWarnings("unchecked")
120+
AbstractBeanDefinition beanDefinition = new RootBeanDefinition(GatewayProxyFactoryBean.class,
121+
() -> {
122+
GatewayProxyFactoryBean proxyFactoryBean = new GatewayProxyFactoryBean(serviceInterface);
123+
if (StringUtils.hasText(defaultRequestChannel)) {
124+
proxyFactoryBean.setDefaultRequestChannelName(defaultRequestChannel);
125+
}
126+
if (StringUtils.hasText(defaultReplyChannel)) {
127+
proxyFactoryBean.setDefaultReplyChannelName(defaultReplyChannel);
128+
}
129+
if (StringUtils.hasText(errorChannel)) {
130+
proxyFactoryBean.setErrorChannelName(errorChannel);
131+
}
132+
if (StringUtils.hasText(proxyDefaultMethods)) {
133+
boolean actualProxyDefaultMethods =
134+
Boolean.parseBoolean(embeddedValueResolver.resolveStringValue(proxyDefaultMethods));
135+
proxyFactoryBean.setProxyDefaultMethods(actualProxyDefaultMethods);
136+
}
137+
if (StringUtils.hasText(mapper)) {
138+
proxyFactoryBean.setMapper(beanFactory.getBean(mapper, MethodArgsMessageMapper.class));
139+
}
114140

115-
if (hasDefaultHeaders) {
116-
Map<String, Object> headerExpressions = new ManagedMap<>();
117-
for (Map<String, Object> header : defaultHeaders) {
118-
String headerValue = (String) header.get("value");
119-
String headerExpression = (String) header.get("expression");
120-
boolean hasValue = StringUtils.hasText(headerValue);
141+
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
142+
proxyFactoryBean.setAsyncExecutor(null);
143+
}
144+
else if (StringUtils.hasText(asyncExecutor)) {
145+
proxyFactoryBean.setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
146+
}
121147

122-
if (hasValue == StringUtils.hasText(headerExpression)) {
123-
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
124-
"is required on a gateway's header.");
148+
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
149+
GatewayMethodMetadata globalMethodMetadata =
150+
createGlobalMethodMetadata(defaultPayloadExpression, defaultHeaders,
151+
hasDefaultPayloadExpression, hasDefaultHeaders, embeddedValueResolver);
152+
proxyFactoryBean.setGlobalMethodMetadata(globalMethodMetadata);
125153
}
126154

127-
BeanDefinition expressionDef =
128-
new RootBeanDefinition(hasValue ? LiteralExpression.class : ExpressionFactoryBean.class);
129-
expressionDef.getConstructorArgumentValues()
130-
.addGenericArgumentValue(hasValue ? headerValue : headerExpression);
155+
Map<String, AbstractBeanDefinition> methodDefinitions =
156+
(Map<String, AbstractBeanDefinition>) gatewayAttributes.get("methods");
131157

132-
headerExpressions.put((String) header.get("name"), expressionDef);
133-
}
134-
methodMetadataBuilder.addPropertyValue("headerExpressions", headerExpressions);
135-
}
158+
if (methodDefinitions != null) {
159+
Map<String, GatewayMethodMetadata> methodMetadataMap =
160+
methodDefinitions.entrySet()
161+
.stream()
162+
.collect(Collectors.toMap(Entry::getKey,
163+
entry -> (GatewayMethodMetadata) entry.getValue().getInstanceSupplier().get()));
136164

137-
gatewayProxyBuilder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition());
138-
}
165+
proxyFactoryBean.setMethodMetadataMap(methodMetadataMap);
166+
}
139167

168+
String actualDefaultRequestTimeout =
169+
embeddedValueResolver.resolveStringValue(
170+
(String) gatewayAttributes.get("defaultRequestTimeout"));
171+
if (actualDefaultRequestTimeout != null) {
172+
proxyFactoryBean.setDefaultRequestTimeoutExpressionString(actualDefaultRequestTimeout);
173+
}
174+
String actualDefaultReplyTimeout =
175+
embeddedValueResolver.resolveStringValue(
176+
(String) gatewayAttributes.get("defaultReplyTimeout"));
177+
if (actualDefaultReplyTimeout != null) {
178+
proxyFactoryBean.setDefaultReplyTimeoutExpressionString(actualDefaultReplyTimeout);
179+
}
180+
return proxyFactoryBean;
181+
});
140182

141-
if (StringUtils.hasText(defaultRequestChannel)) {
142-
gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel);
143-
}
144-
if (StringUtils.hasText(defaultReplyChannel)) {
145-
gatewayProxyBuilder.addPropertyValue("defaultReplyChannelName", defaultReplyChannel);
183+
String id = (String) gatewayAttributes.get("name");
184+
if (!StringUtils.hasText(id)) {
185+
String serviceInterfaceName = serviceInterface.getName();
186+
id = Introspector.decapitalize(serviceInterfaceName.substring(serviceInterfaceName.lastIndexOf('.') + 1));
146187
}
147-
if (StringUtils.hasText(errorChannel)) {
148-
gatewayProxyBuilder.addPropertyValue("errorChannelName", errorChannel);
188+
189+
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, serviceInterface);
190+
return new BeanDefinitionHolder(beanDefinition, id);
191+
}
192+
193+
private static ConfigurableBeanFactory obtainBeanFactory(BeanDefinitionRegistry registry) {
194+
if (registry instanceof ConfigurableBeanFactory) {
195+
return (ConfigurableBeanFactory) registry;
149196
}
150-
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
151-
gatewayProxyBuilder.addPropertyValue("asyncExecutor", null);
197+
else if (registry instanceof ConfigurableApplicationContext) {
198+
return ((ConfigurableApplicationContext) registry).getBeanFactory();
152199
}
153-
else if (StringUtils.hasText(asyncExecutor)) {
154-
gatewayProxyBuilder.addPropertyReference("asyncExecutor", asyncExecutor);
200+
throw new IllegalArgumentException("The provided 'BeanDefinitionRegistry' must be an instance " +
201+
"of 'ConfigurableBeanFactory' or 'ConfigurableApplicationContext', but given is: "
202+
+ registry.getClass());
203+
}
204+
205+
private static Class<?> getServiceInterface(String serviceInterface, ConfigurableBeanFactory beanFactory) {
206+
String actualServiceInterface = beanFactory.resolveEmbeddedValue(serviceInterface);
207+
if (!StringUtils.hasText(actualServiceInterface)) {
208+
return org.springframework.integration.gateway.RequestReplyExchanger.class;
155209
}
156-
if (StringUtils.hasText(mapper)) {
157-
gatewayProxyBuilder.addPropertyReference("mapper", mapper);
210+
try {
211+
return ClassUtils.forName(actualServiceInterface, beanFactory.getBeanClassLoader());
158212
}
159-
if (StringUtils.hasText(proxyDefaultMethods)) {
160-
gatewayProxyBuilder.addPropertyValue(PROXY_DEFAULT_METHODS_ATTR, proxyDefaultMethods);
213+
catch (ClassNotFoundException ex) {
214+
throw new BeanDefinitionStoreException("Cannot parse class for service interface", ex);
161215
}
216+
}
162217

163-
gatewayProxyBuilder.addPropertyValue("defaultRequestTimeoutExpressionString",
164-
gatewayAttributes.get("defaultRequestTimeout"));
165-
gatewayProxyBuilder.addPropertyValue("defaultReplyTimeoutExpressionString",
166-
gatewayAttributes.get("defaultReplyTimeout"));
167-
gatewayProxyBuilder.addPropertyValue("methodMetadataMap", gatewayAttributes.get("methods"));
218+
private static GatewayMethodMetadata createGlobalMethodMetadata(String defaultPayloadExpression,
219+
Map<String, Object>[] defaultHeaders, boolean hasDefaultPayloadExpression,
220+
boolean hasDefaultHeaders, EmbeddedValueResolver embeddedValueResolver) {
168221

222+
GatewayMethodMetadata gatewayMethodMetadata = new GatewayMethodMetadata();
169223

170-
String serviceInterface = (String) gatewayAttributes.get("serviceInterface");
171-
if (!StringUtils.hasText(serviceInterface)) {
172-
serviceInterface = "org.springframework.integration.gateway.RequestReplyExchanger";
173-
}
174-
String id = (String) gatewayAttributes.get("name");
175-
if (!StringUtils.hasText(id)) {
176-
id = Introspector.decapitalize(serviceInterface.substring(serviceInterface.lastIndexOf('.') + 1));
224+
if (hasDefaultPayloadExpression) {
225+
String actualPayloadExpression = embeddedValueResolver.resolveStringValue(defaultPayloadExpression);
226+
gatewayMethodMetadata.setPayloadExpression(EXPRESSION_PARSER.parseExpression(actualPayloadExpression));
177227
}
178228

179-
gatewayProxyBuilder.addConstructorArgValue(serviceInterface);
229+
if (hasDefaultHeaders) {
230+
Map<String, Expression> headerExpressions = new HashMap<>();
231+
for (Map<String, Object> header : defaultHeaders) {
232+
String headerValue = (String) header.get("value");
233+
String headerExpression = (String) header.get("expression");
234+
boolean hasValue = StringUtils.hasText(headerValue);
180235

181-
AbstractBeanDefinition beanDefinition = gatewayProxyBuilder.getBeanDefinition();
182-
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, serviceInterface);
183-
return new BeanDefinitionHolder(beanDefinition, id);
236+
if (hasValue == StringUtils.hasText(headerExpression)) {
237+
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
238+
"is required on a gateway's header.");
239+
}
240+
241+
Expression expression =
242+
hasValue
243+
? new LiteralExpression(embeddedValueResolver.resolveStringValue(headerValue))
244+
: EXPRESSION_PARSER.parseExpression(
245+
embeddedValueResolver.resolveStringValue(headerExpression));
246+
247+
headerExpressions.put((String) header.get("name"), expression);
248+
}
249+
gatewayMethodMetadata.setHeaderExpressions(headerExpressions);
250+
}
251+
return gatewayMethodMetadata;
184252
}
185253

186254
/**

0 commit comments

Comments
 (0)