INT-4489: MessageHistory: Allow runtime beans

JIRA: https://jira.spring.io/browse/INT-4489

Make `MessageHistoryConfigurer` as a `BeanPostProcessor` and apply
tracking logic to processed beans if it is already started.

**Cherry-pick to 5.0.x**
This commit is contained in:
Artem Bilan
2018-06-22 13:26:08 -04:00
committed by Gary Russell
parent e6a35c4a60
commit 07c32ae93b
3 changed files with 75 additions and 47 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -18,8 +18,8 @@ package org.springframework.integration.history;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.support.management.IntegrationManagedResource;
@@ -49,26 +50,24 @@ import org.springframework.util.StringUtils;
*/
@ManagedResource
@IntegrationManagedResource
public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware {
public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAware, BeanPostProcessor {
private final Log logger = LogFactory.getLog(this.getClass());
private volatile String[] componentNamePatterns = new String[] { "*" };
private final Set<TrackableComponent> currentlyTrackedComponents = ConcurrentHashMap.newKeySet();
private volatile boolean componentNamePatternsExplicitlySet;
private String[] componentNamePatterns = new String[] { "*" };
private final Set<String> currentlyTrackedComponentNames = new HashSet<String>();
private boolean componentNamePatternsExplicitlySet;
private volatile BeanFactory beanFactory;
private ListableBeanFactory beanFactory;
private boolean autoStartup = true;
private int phase = Integer.MIN_VALUE;
private volatile boolean running;
private volatile boolean autoStartup = true;
private final int phase = Integer.MIN_VALUE;
private final Object lifecycleMonitor = new Object();
/**
* The patterns for which components will be tracked; default '*' (all trackable
@@ -84,10 +83,10 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
}
Arrays.sort(trimmedAndSortedComponentNamePatterns);
Assert.isTrue(!this.componentNamePatternsExplicitlySet
|| Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns),
"When more than one message history definition " +
"(@EnableMessageHistory or <message-history>)" +
" is found in the context, they all must have the same 'componentNamePatterns'");
|| Arrays.equals(this.componentNamePatterns, trimmedAndSortedComponentNamePatterns),
"When more than one message history definition " +
"(@EnableMessageHistory or <message-history>)" +
" is found in the context, they all must have the same 'componentNamePatterns'");
this.componentNamePatterns = trimmedAndSortedComponentNamePatterns;
this.componentNamePatternsExplicitlySet = true;
}
@@ -136,13 +135,30 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
"The provided 'beanFactory' must be of 'ListableBeanFactory' type.");
this.beanFactory = (ListableBeanFactory) beanFactory;
}
private static Collection<TrackableComponent> getTrackableComponents(ListableBeanFactory beanFactory) {
return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TrackableComponent && this.running) {
trackComponentIfAny((TrackableComponent) bean);
}
return bean;
}
private void trackComponentIfAny(TrackableComponent component) {
String componentName = component.getComponentName();
boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName);
component.setShouldTrack(shouldTrack);
if (shouldTrack) {
this.currentlyTrackedComponents.add(component);
if (this.logger.isInfoEnabled()) {
this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'");
}
}
}
/*
* SmartLifecycle implementation
@@ -153,11 +169,19 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
return this.running;
}
public void setAutoStartup(boolean autoStartup) {
this.autoStartup = autoStartup;
}
@Override
public boolean isAutoStartup() {
return this.autoStartup;
}
public void setPhase(int phase) {
this.phase = phase;
}
@Override
public int getPhase() {
return this.phase;
@@ -166,20 +190,12 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
@ManagedOperation
@Override
public void start() {
synchronized (this.lifecycleMonitor) {
if (!this.running && this.beanFactory instanceof ListableBeanFactory) {
for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) {
String componentName = component.getComponentName();
boolean shouldTrack = PatternMatchUtils.simpleMatch(this.componentNamePatterns, componentName);
component.setShouldTrack(shouldTrack);
if (shouldTrack) {
this.currentlyTrackedComponentNames.add(componentName);
if (this.logger.isInfoEnabled()) {
this.logger.info("Enabling MessageHistory tracking for component '" + componentName + "'");
}
}
synchronized (this.currentlyTrackedComponents) {
if (!this.running) {
for (TrackableComponent component : getTrackableComponents(this.beanFactory)) {
trackComponentIfAny(component);
this.running = true;
}
this.running = true;
}
}
}
@@ -187,20 +203,19 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
@ManagedOperation
@Override
public void stop() {
synchronized (this.lifecycleMonitor) {
if (this.running && this.beanFactory instanceof ListableBeanFactory) {
for (TrackableComponent component : getTrackableComponents((ListableBeanFactory) this.beanFactory)) {
String componentName = component.getComponentName();
if (this.currentlyTrackedComponentNames.contains(componentName)) {
component.setShouldTrack(false);
if (this.logger.isInfoEnabled()) {
this.logger.info("Disabling MessageHistory tracking for component '" + componentName + "'");
}
synchronized (this.currentlyTrackedComponents) {
if (this.running) {
this.currentlyTrackedComponents.forEach(component -> {
component.setShouldTrack(false);
if (this.logger.isInfoEnabled()) {
this.logger.info("Disabling MessageHistory tracking for component '"
+ component.getComponentName() + "'");
}
}
this.currentlyTrackedComponentNames.clear();
this.running = false;
});
this.currentlyTrackedComponents.clear();
this.componentNamePatternsExplicitlySet = false; // allow pattern changes
this.running = false;
}
}
}
@@ -211,4 +226,8 @@ public class MessageHistoryConfigurer implements SmartLifecycle, BeanFactoryAwar
callback.run();
}
private static Collection<TrackableComponent> getTrackableComponents(ListableBeanFactory beanFactory) {
return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, TrackableComponent.class).values();
}
}

View File

@@ -41,6 +41,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,6 +59,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.config.EnableMessageHistory;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
@@ -71,6 +73,7 @@ import org.springframework.integration.dsl.context.IntegrationFlowContext;
import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.messaging.Message;
@@ -254,6 +257,12 @@ public class ManualFlowTests {
assertNotNull(receive);
assertEquals("test", receive.getPayload());
MessageHistory messageHistory = MessageHistory.read(receive);
assertNotNull(messageHistory);
String messageHistoryString = messageHistory.toString();
assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.input"));
assertThat(messageHistoryString, Matchers.containsString("dynamicFlow.subFlow#0.channel#1"));
this.integrationFlowContext.remove("dynamicFlow");
}
@@ -475,6 +484,7 @@ public class ManualFlowTests {
@Configuration
@EnableIntegration
@EnableMessageHistory
public static class RootConfiguration {
@Bean

View File

@@ -73,8 +73,7 @@ This feature might be useful to temporarily turn on history to analyze a system.
The MBean's object name is `"<domain>:name=messageHistoryConfigurer,type=MessageHistoryConfigurer"`.
IMPORTANT: If multiple beans (declared by `@EnableMessageHistory` and/or `<message-history/>`) they all must have identical component name patterns (when trimmed and sorted).
*Do not use a generic
`<bean/>` definition for the `MessageHistoryConfigurer`*.
*Do not use a generic `<bean/>` definition for the `MessageHistoryConfigurer`*.
NOTE: Remember that by definition the Message History header is immutable (you can't re-write history, although some try).
Therefore, when writing Message History values, the components are either creating brand new Messages (when the component is an origin), or they are copying the history from a request Message, modifying it and setting the new list on a reply Message.