From 8305c6fec3459789b2904751e07075bc4e607035 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 25 Oct 2018 15:30:32 -0400 Subject: [PATCH] INT-4322: Log merged global properties on startup (#2612) * INT-4322: Log merged global properties on startup JIRA: https://jira.spring.io/browse/INT-4322 Now we have DEBUG message in logs like this: ``` Spring Integration global properties: spring.integration.endpoints.noAutoStartup=fooService* spring.integration.taskScheduler.poolSize=20 spring.integration.channels.maxUnicastSubscribers=0x7fffffff spring.integration.channels.autoCreate=true spring.integration.channels.maxBroadcastSubscribers=0x7fffffff spring.integration.readOnly.headers= spring.integration.messagingTemplate.throwExceptionOnLateReply=true ``` * Also register a `mergedIntegrationGlobalProperties` bean on a first `IntegrationContextUtils.getIntegrationProperties()` call to avoid a new `Properties` object on each call * * Fix `TestUtils` to register a `BeanDefinition` instead of `registerSingleton()` and an explicit `afterPropertiesSet()` call * * Document mentioned changes * Some polishing for `whats-new.adoc` --- ...ltConfiguringBeanFactoryPostProcessor.java | 26 +++++++++++-- .../context/IntegrationContextUtils.java | 34 ++++++++++++++--- .../integration/test/util/TestUtils.java | 35 ++++------------- src/reference/asciidoc/configuration.adoc | 38 +++++++++++-------- src/reference/asciidoc/whats-new.adoc | 10 ++++- 5 files changed, 89 insertions(+), 54 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java index a51a4a3fa5..d013796a0b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/DefaultConfiguringBeanFactoryPostProcessor.java @@ -17,10 +17,13 @@ package org.springframework.integration.config; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Properties; import java.util.Set; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; @@ -29,6 +32,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -80,7 +84,8 @@ import org.springframework.util.ClassUtils; * * @see IntegrationContextUtils */ -class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware { +class DefaultConfiguringBeanFactoryPostProcessor + implements BeanFactoryPostProcessor, BeanClassLoaderAware, SmartInitializingSingleton { private static final Log logger = LogFactory.getLog(DefaultConfiguringBeanFactoryPostProcessor.class); @@ -128,6 +133,18 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce } } + @Override + public void afterSingletonsInstantiated() { + if (logger.isDebugEnabled()) { + Properties integrationProperties = IntegrationContextUtils.getIntegrationProperties(this.beanFactory); + + StringWriter writer = new StringWriter(); + integrationProperties.list(new PrintWriter(writer)); + StringBuffer propertiesBuffer = writer.getBuffer() + .delete(0, "-- listing properties --".length()); + logger.debug("\nSpring Integration global properties:\n" + propertiesBuffer); + } + } /** * Register a null channel in the application context. @@ -135,7 +152,7 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce */ private void registerNullChannel() { if (this.beanFactory.containsBean(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME)) { - BeanDefinition nullChannelDefinition = null; + BeanDefinition nullChannelDefinition; if (this.beanFactory.containsBeanDefinition(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME)) { nullChannelDefinition = this.beanFactory.getBeanDefinition(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME); @@ -319,7 +336,7 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce jsonPathClass = null; logger.warn("The '#jsonPath' SpEL function cannot be registered. " + "An old json-path.jar version is detected in the classpath." + - "At least 1.2.0 is required; see version information at: " + + "At least 2.4.0 is required; see version information at: " + "https://github.com/jayway/JsonPath/releases", e); } @@ -482,7 +499,8 @@ class DefaultConfiguringBeanFactoryPostProcessor implements BeanFactoryPostProce private BeanDefinition internalArgumentResolversBuilder(boolean listCapable) { ManagedList resolvers = new ManagedList<>(); resolvers.add(new RootBeanDefinition(PayloadExpressionArgumentResolver.class)); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(NullAwarePayloadArgumentResolver.class); + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(NullAwarePayloadArgumentResolver.class); builder.addConstructorArgReference(IntegrationContextUtils.ARGUMENT_RESOLVER_MESSAGE_CONVERTER_BEAN_NAME); // TODO Validator ? resolvers.add(builder.getBeanDefinition()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java index 8d7ba5e458..5adc495415 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/context/IntegrationContextUtils.java @@ -19,6 +19,8 @@ package org.springframework.integration.context; import java.util.Properties; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.config.IntegrationConfigUtils; @@ -57,6 +59,8 @@ public abstract class IntegrationContextUtils { public static final String INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME = "integrationGlobalProperties"; + public static final String MERGED_INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME = "mergedIntegrationGlobalProperties"; + public static final String CHANNEL_INITIALIZER_BEAN_NAME = "channelInitializer"; public static final String AUTO_CREATE_CHANNEL_CANDIDATES_BEAN_NAME = "$autoCreateChannelCandidates"; @@ -176,15 +180,33 @@ public abstract class IntegrationContextUtils { * provided {@code #beanFactory} or provided {@code #beanFactory} is null. */ public static Properties getIntegrationProperties(BeanFactory beanFactory) { - Properties properties = new Properties(); - properties.putAll(IntegrationProperties.defaults()); + Properties properties; if (beanFactory != null) { - Properties userProperties = - getBeanOfType(beanFactory, INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, Properties.class); - if (userProperties != null) { - properties.putAll(userProperties); + properties = getBeanOfType(beanFactory, MERGED_INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, Properties.class); + if (properties == null) { + Properties propertiesToRegister = new Properties(); + propertiesToRegister.putAll(IntegrationProperties.defaults()); + Properties userProperties = + getBeanOfType(beanFactory, INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, Properties.class); + if (userProperties != null) { + propertiesToRegister.putAll(userProperties); + } + + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + RootBeanDefinition beanDefinition = new RootBeanDefinition(Properties.class); + beanDefinition.setInstanceSupplier(() -> propertiesToRegister); + + registry.registerBeanDefinition(MERGED_INTEGRATION_GLOBAL_PROPERTIES_BEAN_NAME, beanDefinition); + } + + properties = propertiesToRegister; } } + else { + properties = new Properties(); + properties.putAll(IntegrationProperties.defaults()); + } return properties; } diff --git a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java index 9c2b69de20..1bea5e83b9 100644 --- a/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java +++ b/spring-integration-test-support/src/main/java/org/springframework/integration/test/util/TestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -28,12 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.DirectFieldAccessor; -import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.support.GenericApplicationContext; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.messaging.Message; @@ -134,30 +129,14 @@ public abstract class TestUtils { return scheduler; } + @SuppressWarnings("unchecked") private static void registerBean(String beanName, Object bean, BeanFactory beanFactory) { Assert.notNull(beanName, "bean name must not be null"); - ConfigurableListableBeanFactory configurableListableBeanFactory = null; - if (beanFactory instanceof ConfigurableListableBeanFactory) { - configurableListableBeanFactory = (ConfigurableListableBeanFactory) beanFactory; - } - else if (beanFactory instanceof GenericApplicationContext) { - configurableListableBeanFactory = ((GenericApplicationContext) beanFactory).getBeanFactory(); - } - if (bean instanceof BeanNameAware) { - ((BeanNameAware) bean).setBeanName(beanName); - } - if (bean instanceof BeanFactoryAware) { - ((BeanFactoryAware) bean).setBeanFactory(beanFactory); - } - if (bean instanceof InitializingBean) { - try { - ((InitializingBean) bean).afterPropertiesSet(); - } - catch (Exception e) { - throw new FatalBeanException("failed to register bean with test context", e); - } - } - configurableListableBeanFactory.registerSingleton(beanName, bean); //NOSONAR false positive + Assert.isInstanceOf(GenericApplicationContext.class, beanFactory, + "beanFactory must be an instance of GenericApplicationContext"); + GenericApplicationContext applicationContext = (GenericApplicationContext) beanFactory; + + applicationContext.registerBean(beanName, (Class) bean.getClass(), () -> bean); } diff --git a/src/reference/asciidoc/configuration.adoc b/src/reference/asciidoc/configuration.adoc index a876fdc1ab..6a0af74370 100644 --- a/src/reference/asciidoc/configuration.adoc +++ b/src/reference/asciidoc/configuration.adoc @@ -133,9 +133,7 @@ Such endpoints have no poller configuration, since their handlers are invoked di [IMPORTANT] ===== -When running in a JEE container, you may need to use Spring's `TimerManagerTaskScheduler`, as described -http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-task-scheduler-implementations[here], -instead of the default `taskScheduler`. +When running in a JEE container, you may need to use Spring's `TimerManagerTaskScheduler`, as described http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-task-scheduler-implementations[here], instead of the default `taskScheduler`. To do so, define a bean with the appropriate JNDI name for your environment, as the following example shows: ==== @@ -154,8 +152,7 @@ The next section describes what happens if exceptions occur within the asynchron [[namespace-errorhandler]] === Error Handling -As described in the <> at the very beginning of this manual, one of the main motivations behind a message-oriented -framework such as Spring Integration is to promote loose coupling between components. +As described in the <> at the very beginning of this manual, one of the main motivations behind a message-oriented framework such as Spring Integration is to promote loose coupling between components. The message channel plays an important role, in that producers and consumers do not have to know about each other. However, the advantages also have some drawbacks. Some things become more complicated in a loosely coupled environment, and one example is error handling. @@ -200,10 +197,8 @@ The following example shows how to define an error channel backed by a queue wit NOTE: The default error channel is a `PublishSubscribeChannel`. -The most important thing to understand here is that the messaging-based error handling applies only to exceptions -that are thrown by a Spring Integration task that is executing within a `TaskExecutor`. -This does not apply to exceptions thrown by a handler that operates within the same thread as the sender (for example, -through a `DirectChannel` as described earlier in this section). +The most important thing to understand here is that the messaging-based error handling applies only to exceptions that are thrown by a Spring Integration task that is executing within a `TaskExecutor`. +This does not apply to exceptions thrown by a handler that operates within the same thread as the sender (for example, through a `DirectChannel` as described earlier in this section). NOTE: When exceptions occur in a scheduled poller task's execution, those exceptions are wrapped in `ErrorMessage` instances and sent to the 'errorChannel' as well. @@ -227,8 +222,7 @@ The `DefaultErrorMessageStrategy` does exactly that. Certain global framework properties can be overridden by providing a properties file on the classpath. -The default properties can be found in `/META-INF/spring.integration.default.properties` in the `spring-integration-core` -jar. +The default properties can be found in `/META-INF/spring.integration.default.properties` in the `spring-integration-core` jar. You can see them on GitHub https://github.com/spring-projects/spring-integration/blob/master/spring-integration-core/src/main/resources/META-INF/spring.integration.default.properties[here]. The following listing shows the default values: @@ -245,8 +239,7 @@ spring.integration.endpoints.noAutoStartup= <7> spring.integration.postProcessDynamicBeans=false <8> ---- -<1> When true, `input-channel` instances are automatically declared as `DirectChannel` instances when not explicitly found in the -application context. +<1> When true, `input-channel` instances are automatically declared as `DirectChannel` instances when not explicitly found in the application context. <2> Sets the default number of subscribers allowed on, for example, a `DirectChannel`. It can be used to avoid inadvertently subscribing multiple endpoints to the same channel. @@ -259,8 +252,7 @@ You can override it on individual channels by setting the `max-subscribers` attr <4> The number of threads available in the default `taskScheduler` bean. See <>. -<5> When `true`, messages that arrive at a gateway reply channel throw an exception when the gateway is not -expecting a reply (because the sending thread has timed out or already received a reply). +<5> When `true`, messages that arrive at a gateway reply channel throw an exception when the gateway is not expecting a reply (because the sending thread has timed out or already received a reply). <6> A comma-separated list of message header names that should not be populated into `Message` instances during a header copying operation. The list is used by the `DefaultMessageBuilderFactory` bean and propagated to the `IntegrationMessageHeaderAccessor` instances (see <>) used to build messages via `MessageBuilder` (see <>). @@ -279,6 +271,22 @@ Since version 4.3.15. These properties can be overridden by adding a `/META-INF/spring.integration.properties` file to the classpath. You need not provide all the properties -- only those that you want to override. +Starting with version 5.1, all the merged global properties are printed in the logs after application context startup when a `DEBUG` logic level is turned on for the `org.springframework.integration` category. +The output looks like this: +==== +[source] +---- +Spring Integration global properties: + +spring.integration.endpoints.noAutoStartup=fooService* +spring.integration.taskScheduler.poolSize=20 +spring.integration.channels.maxUnicastSubscribers=0x7fffffff +spring.integration.channels.autoCreate=true +spring.integration.channels.maxBroadcastSubscribers=0x7fffffff +spring.integration.readOnly.headers= +spring.integration.messagingTemplate.throwExceptionOnLateReply=true +---- + [[annotations]] === Annotation Support diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index a4fee47c75..3de87adf6f 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -45,6 +45,8 @@ The following changes have been made in version 5.1: * <> * <> * <> +* <> +* <> [[x5.1-java-dsl]] ==== Java DSL @@ -218,4 +220,10 @@ See <> for more information. === Integration Graph Customization It is now possible to add additional properties to the `IntegrationNode` s via `Function> additionalPropertiesCallback` on the `IntegrationGraphServer`. -See <<<>>> for more information. +See <> for more information. + +[[x51.-x51.-global-properties]] +=== Integration Global Properties + +The Integration global properties (including defaults) can now be printed in the logs, when a `DEBUG` logic level is turned on for the `org.springframework.integration` category. +See <> for more information.