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`
This commit is contained in:
Artem Bilan
2018-10-25 15:30:32 -04:00
committed by Gary Russell
parent 8b54277a34
commit 8305c6fec3
5 changed files with 89 additions and 54 deletions

View File

@@ -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<BeanDefinition> 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());

View File

@@ -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;
}

View File

@@ -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<Object>) bean.getClass(), () -> bean);
}

View File

@@ -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 <<overview,overview>> 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 <<overview,overview>> 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 <<namespace-taskscheduler>>.
<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 <<message-header-accessor>>) used to build messages via `MessageBuilder` (see <<message-builder>>).
@@ -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

View File

@@ -45,6 +45,8 @@ The following changes have been made in version 5.1:
* <<x5.1-integration-flows-generated-bean-names>>
* <<x5.1-aggregator>>
* <<x5.1-publisher>>
* <<x51.-integration-graph>>
* <<x51.-global-properties>>
[[x5.1-java-dsl]]
==== Java DSL
@@ -218,4 +220,10 @@ See <<micrometer-integration>> for more information.
=== Integration Graph Customization
It is now possible to add additional properties to the `IntegrationNode` s via `Function<NamedComponent, Map<String, Object>> additionalPropertiesCallback` on the `IntegrationGraphServer`.
See <<<<integration-graph>>>> for more information.
See <<integration-graph>> 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 <<global-properties>> for more information.