Skip to content

Commit 3f0c578

Browse files
authored
GH-3502: More refactoring to avoid reflection (#3532)
* GH-3502: More refactoring to avoid reflection Fixes #3502 * Move `ChannelInitializer` bean registration into an `AbstractIntegrationNamespaceHandler` - it was never used for annotations and Java DSL... * Rework `IntegrationFlows.fromSupplier()` to call a provided `Supplier` directly - not via reflection in the `MethodInvokingMessageSource` * Resolve new Sonar smells * Rework `EndpointSpec` to accept an expected factory bean instance via ctor arg instead of reflection * Rework `Jackson2JsonObjectMapper` to use well-known module instances directly - not via reflection from their class names * * Revert `DefaultMethodInvokingMethodInterceptor.methodHandleCache` property definition wrap
1 parent 27e1f3a commit 3f0c578

21 files changed

+370
-306
lines changed

build.gradle

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,15 @@ project('spring-integration-core') {
440440
exclude group: 'org.springframework'
441441
}
442442
api 'io.projectreactor:reactor-core'
443+
443444
optionalApi 'com.fasterxml.jackson.core:jackson-databind'
445+
optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8'
446+
optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
447+
optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-joda'
448+
optionalApi ('com.fasterxml.jackson.module:jackson-module-kotlin') {
449+
exclude group: 'org.jetbrains.kotlin'
450+
}
451+
444452
optionalApi "com.jayway.jsonpath:json-path:$jsonpathVersion"
445453
optionalApi "com.esotericsoftware:kryo-shaded:$kryoShadedVersion"
446454
optionalApi "io.micrometer:micrometer-core:$micrometerVersion"
@@ -449,10 +457,6 @@ project('spring-integration-core') {
449457
optionalApi 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
450458

451459
testImplementation "org.aspectj:aspectjweaver:$aspectjVersion"
452-
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
453-
testRuntime ('com.fasterxml.jackson.module:jackson-module-kotlin') {
454-
exclude group: 'org.jetbrains.kotlin'
455-
}
456460
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
457461
}
458462
}

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-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.
@@ -40,16 +40,20 @@
4040
*
4141
* @author Oleg Zhurakousky
4242
* @author Gary Russell
43+
* @author Artem Bilan
44+
*
4345
* @since 2.1.1
4446
*/
45-
final class ChannelInitializer implements BeanFactoryAware, InitializingBean {
47+
public final class ChannelInitializer implements BeanFactoryAware, InitializingBean {
4648

47-
private final Log logger = LogFactory.getLog(this.getClass());
49+
private static final Log LOGGER = LogFactory.getLog(ChannelInitializer.class);
4850

4951
private volatile BeanFactory beanFactory;
5052

5153
private volatile boolean autoCreate = true;
5254

55+
ChannelInitializer() {
56+
}
5357

5458
public void setAutoCreate(boolean autoCreate) {
5559
this.autoCreate = autoCreate;
@@ -68,17 +72,18 @@ public void afterPropertiesSet() {
6872
}
6973
else {
7074
AutoCreateCandidatesCollector channelCandidatesCollector =
71-
this.beanFactory.getBean(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME, AutoCreateCandidatesCollector.class);
72-
Assert.notNull(channelCandidatesCollector, "Failed to locate '" + IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME);
75+
this.beanFactory.getBean(IntegrationContextUtils.AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME,
76+
AutoCreateCandidatesCollector.class);
7377
// at this point channelNames are all resolved with placeholders and SpEL
7478
Collection<String> channelNames = channelCandidatesCollector.getChannelNames();
7579
if (channelNames != null) {
7680
for (String channelName : channelNames) {
7781
if (!this.beanFactory.containsBean(channelName)) {
78-
if (this.logger.isDebugEnabled()) {
79-
this.logger.debug("Auto-creating channel '" + channelName + "' as DirectChannel");
82+
if (LOGGER.isDebugEnabled()) {
83+
LOGGER.debug("Auto-creating channel '" + channelName + "' as DirectChannel");
8084
}
81-
IntegrationConfigUtils.autoCreateDirectChannel(channelName, (BeanDefinitionRegistry) this.beanFactory);
85+
IntegrationConfigUtils.autoCreateDirectChannel(channelName,
86+
(BeanDefinitionRegistry) this.beanFactory);
8287
}
8388
}
8489
}
@@ -88,7 +93,7 @@ public void afterPropertiesSet() {
8893
/*
8994
* Collects candidate channel names to be auto-created by ChannelInitializer
9095
*/
91-
static class AutoCreateCandidatesCollector {
96+
public static class AutoCreateCandidatesCollector {
9297

9398
private final Collection<String> channelNames;
9499

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

Lines changed: 66 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.beans.factory.HierarchicalBeanFactory;
3333
import org.springframework.beans.factory.SmartInitializingSingleton;
3434
import org.springframework.beans.factory.config.BeanDefinition;
35-
import org.springframework.beans.factory.config.BeanExpressionContext;
3635
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3736
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3837
import org.springframework.beans.factory.config.PropertiesFactoryBean;
@@ -58,6 +57,7 @@
5857
import org.springframework.integration.handler.support.MapArgumentResolver;
5958
import org.springframework.integration.handler.support.PayloadExpressionArgumentResolver;
6059
import org.springframework.integration.handler.support.PayloadsArgumentResolver;
60+
import org.springframework.integration.json.JsonNodeWrapperToJsonNodeConverter;
6161
import org.springframework.integration.support.DefaultMessageBuilderFactory;
6262
import org.springframework.integration.support.NullAwarePayloadArgumentResolver;
6363
import org.springframework.integration.support.SmartLifecycleRoleController;
@@ -67,14 +67,14 @@
6767
import org.springframework.integration.support.converter.DefaultDatatypeChannelMessageConverter;
6868
import org.springframework.integration.support.json.JacksonPresent;
6969
import org.springframework.integration.support.utils.IntegrationUtils;
70-
import org.springframework.lang.Nullable;
7170
import org.springframework.messaging.MessageHandler;
7271
import org.springframework.messaging.converter.MessageConverter;
7372
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
7473
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
7574
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
7675
import org.springframework.util.ClassUtils;
7776
import org.springframework.util.ErrorHandler;
77+
import org.springframework.util.StringUtils;
7878

7979
/**
8080
* A {@link BeanFactoryPostProcessor} implementation that registers bean definitions
@@ -100,12 +100,44 @@ class DefaultConfiguringBeanFactoryPostProcessor
100100

101101
private static final Set<Integer> REGISTRIES_PROCESSED = new HashSet<>();
102102

103+
104+
private static final Class<?> XPATH_CLASS;
105+
106+
private static final Class<?> JSON_PATH_CLASS;
107+
108+
static {
109+
Class<?> xpathClass = null;
110+
try {
111+
xpathClass = ClassUtils.forName(IntegrationConfigUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils",
112+
ClassUtils.getDefaultClassLoader());
113+
}
114+
catch (@SuppressWarnings("unused") ClassNotFoundException e) {
115+
LOGGER.debug("SpEL function '#xpath' isn't registered: " +
116+
"there is no spring-integration-xml.jar on the classpath.");
117+
}
118+
finally {
119+
XPATH_CLASS = xpathClass;
120+
}
121+
122+
Class<?> jsonPathClass = null;
123+
try {
124+
jsonPathClass = ClassUtils.forName(IntegrationConfigUtils.BASE_PACKAGE + ".json.JsonPathUtils",
125+
ClassUtils.getDefaultClassLoader());
126+
}
127+
catch (@SuppressWarnings("unused") ClassNotFoundException e) {
128+
LOGGER.debug("The '#jsonPath' SpEL function cannot be registered: " +
129+
"there is no jayway json-path.jar on the classpath.");
130+
}
131+
finally {
132+
JSON_PATH_CLASS = jsonPathClass;
133+
}
134+
}
135+
136+
103137
private ClassLoader classLoader;
104138

105139
private ConfigurableListableBeanFactory beanFactory;
106140

107-
private BeanExpressionContext expressionContext;
108-
109141
private BeanDefinitionRegistry registry;
110142

111143
@Override
@@ -117,7 +149,6 @@ public void setBeanClassLoader(ClassLoader classLoader) {
117149
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
118150
if (beanFactory instanceof BeanDefinitionRegistry) {
119151
this.beanFactory = beanFactory;
120-
this.expressionContext = new BeanExpressionContext(beanFactory, null);
121152
this.registry = (BeanDefinitionRegistry) beanFactory;
122153

123154
registerBeanFactoryChannelResolver();
@@ -242,16 +273,14 @@ private void registerErrorChannel() {
242273
}
243274

244275
private PublishSubscribeChannel createErrorChannel() {
245-
String requireSubscribersExpression =
246-
IntegrationProperties.getExpressionFor(IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS);
247-
Boolean requireSubscribers = resolveExpression(requireSubscribersExpression, Boolean.class);
276+
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
277+
String requireSubscribers =
278+
integrationProperties.getProperty(IntegrationProperties.ERROR_CHANNEL_REQUIRE_SUBSCRIBERS);
248279

249-
PublishSubscribeChannel errorChannel = new PublishSubscribeChannel(Boolean.TRUE.equals(requireSubscribers));
280+
PublishSubscribeChannel errorChannel = new PublishSubscribeChannel(Boolean.parseBoolean(requireSubscribers));
250281

251-
String ignoreFailuresExpression =
252-
IntegrationProperties.getExpressionFor(IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES);
253-
Boolean ignoreFailures = resolveExpression(ignoreFailuresExpression, Boolean.class);
254-
errorChannel.setIgnoreFailures(Boolean.TRUE.equals(ignoreFailures));
282+
String ignoreFailures = integrationProperties.getProperty(IntegrationProperties.ERROR_CHANNEL_IGNORE_FAILURES);
283+
errorChannel.setIgnoreFailures(Boolean.parseBoolean(ignoreFailures));
255284

256285
return errorChannel;
257286
}
@@ -323,12 +352,9 @@ private ThreadPoolTaskScheduler createTaskScheduler() {
323352
taskScheduler.setErrorHandler(
324353
this.beanFactory.getBean(ChannelUtils.MESSAGE_PUBLISHING_ERROR_HANDLER_BEAN_NAME, ErrorHandler.class));
325354

326-
String poolSizeExpression =
327-
IntegrationProperties.getExpressionFor(IntegrationProperties.TASK_SCHEDULER_POOL_SIZE);
328-
Integer poolSize = resolveExpression(poolSizeExpression, Integer.class);
329-
if (poolSize != null) {
330-
taskScheduler.setPoolSize(poolSize);
331-
}
355+
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
356+
String poolSize = integrationProperties.getProperty(IntegrationProperties.TASK_SCHEDULER_POOL_SIZE);
357+
taskScheduler.setPoolSize(Integer.parseInt(poolSize));
332358

333359
return taskScheduler;
334360
}
@@ -376,54 +402,21 @@ private void registerBuiltInBeans() {
376402

377403
private void jsonPath(int registryId) throws LinkageError {
378404
String jsonPathBeanName = "jsonPath";
379-
if (!this.beanFactory.containsBean(jsonPathBeanName) && !REGISTRIES_PROCESSED.contains(registryId)) {
380-
Class<?> jsonPathClass = null;
381-
try {
382-
jsonPathClass = ClassUtils.forName("com.jayway.jsonpath.JsonPath", this.classLoader);
383-
}
384-
catch (@SuppressWarnings("unused") ClassNotFoundException e) {
385-
LOGGER.debug("The '#jsonPath' SpEL function cannot be registered: " +
386-
"there is no jayway json-path.jar on the classpath.");
387-
}
405+
if (JSON_PATH_CLASS != null
406+
&& !this.beanFactory.containsBean(jsonPathBeanName)
407+
&& !REGISTRIES_PROCESSED.contains(registryId)) {
388408

389-
if (jsonPathClass != null) {
390-
try {
391-
ClassUtils.forName("com.jayway.jsonpath.Predicate", this.classLoader);
392-
}
393-
catch (ClassNotFoundException ex) {
394-
jsonPathClass = null;
395-
LOGGER.warn(ex, "The '#jsonPath' SpEL function cannot be registered. " +
396-
"An old json-path.jar version is detected in the classpath." +
397-
"At least 2.4.0 is required; see version information at: " +
398-
"https://github.com/jayway/JsonPath/releases");
399-
400-
}
401-
}
402-
403-
if (jsonPathClass != null) {
404-
IntegrationConfigUtils.registerSpelFunctionBean(this.registry, jsonPathBeanName,
405-
IntegrationConfigUtils.BASE_PACKAGE + ".json.JsonPathUtils", "evaluate");
406-
}
409+
IntegrationConfigUtils.registerSpelFunctionBean(this.registry, jsonPathBeanName, JSON_PATH_CLASS, "evaluate");
407410
}
408411
}
409412

410413
private void xpath(int registryId) throws LinkageError {
411414
String xpathBeanName = "xpath";
412-
if (!this.beanFactory.containsBean(xpathBeanName) && !REGISTRIES_PROCESSED.contains(registryId)) {
413-
Class<?> xpathClass = null;
414-
try {
415-
xpathClass = ClassUtils.forName(IntegrationConfigUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils",
416-
this.classLoader);
417-
}
418-
catch (@SuppressWarnings("unused") ClassNotFoundException e) {
419-
LOGGER.debug("SpEL function '#xpath' isn't registered: " +
420-
"there is no spring-integration-xml.jar on the classpath.");
421-
}
415+
if (XPATH_CLASS != null
416+
&& !this.beanFactory.containsBean(xpathBeanName)
417+
&& !REGISTRIES_PROCESSED.contains(registryId)) {
422418

423-
if (xpathClass != null) {
424-
IntegrationConfigUtils.registerSpelFunctionBean(this.registry, xpathBeanName,
425-
IntegrationConfigUtils.BASE_PACKAGE + ".xml.xpath.XPathUtils", "evaluate");
426-
}
419+
IntegrationConfigUtils.registerSpelFunctionBean(this.registry, xpathBeanName, XPATH_CLASS, "evaluate");
427420
}
428421
}
429422

@@ -434,9 +427,9 @@ private void jsonNodeToString(int registryId) {
434427

435428
this.registry.registerBeanDefinition(
436429
IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER,
437-
BeanDefinitionBuilder.genericBeanDefinition(IntegrationConfigUtils.BASE_PACKAGE +
438-
".json.JsonNodeWrapperToJsonNodeConverter")
439-
.getBeanDefinition());
430+
new RootBeanDefinition(JsonNodeWrapperToJsonNodeConverter.class,
431+
JsonNodeWrapperToJsonNodeConverter::new));
432+
440433
INTEGRATION_CONVERTER_INITIALIZER.registerConverter(this.registry,
441434
new RuntimeBeanReference(IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER));
442435
}
@@ -457,21 +450,19 @@ private void registerRoleController() {
457450
*/
458451
private void registerMessageBuilderFactory() {
459452
if (!this.beanFactory.containsBean(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME)) {
460-
BeanDefinition mbfBean =
461-
new RootBeanDefinition(DefaultMessageBuilderFactory.class,
462-
() -> {
463-
DefaultMessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
464-
String readOnlyHeadersExpression =
465-
IntegrationProperties.getExpressionFor(IntegrationProperties.READ_ONLY_HEADERS);
466-
String[] readOnlyHeaders = resolveExpression(readOnlyHeadersExpression, String[].class);
467-
messageBuilderFactory.setReadOnlyHeaders(readOnlyHeaders);
468-
return messageBuilderFactory;
469-
});
470-
471-
this.registry.registerBeanDefinition(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME, mbfBean);
453+
this.registry.registerBeanDefinition(IntegrationUtils.INTEGRATION_MESSAGE_BUILDER_FACTORY_BEAN_NAME,
454+
new RootBeanDefinition(DefaultMessageBuilderFactory.class, this::createDefaultMessageBuilderFactory));
472455
}
473456
}
474457

458+
private DefaultMessageBuilderFactory createDefaultMessageBuilderFactory() {
459+
DefaultMessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();
460+
Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory);
461+
String readOnlyHeaders = integrationProperties.getProperty(IntegrationProperties.READ_ONLY_HEADERS);
462+
messageBuilderFactory.setReadOnlyHeaders(StringUtils.commaDelimitedListToStringArray(readOnlyHeaders));
463+
return messageBuilderFactory;
464+
}
465+
475466
/**
476467
* Register a {@link DefaultHeaderChannelRegistry} if necessary.
477468
*/
@@ -585,10 +576,4 @@ private List<HandlerMethodArgumentResolver> buildArgumentResolvers(boolean listC
585576
return resolvers;
586577
}
587578

588-
@Nullable
589-
private <T> T resolveExpression(String expression, Class<T> expectedType) {
590-
Object value = this.beanFactory.getBeanExpressionResolver().evaluate(expression, this.expressionContext);
591-
return this.beanFactory.getTypeConverter().convertIfNecessary(value, expectedType);
592-
}
593-
594579
}

0 commit comments

Comments
 (0)