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