GH-2748: Add bean definition info into exceptions (#2986)

* GH-2748: Add bean definition info into exceptions

Fixes https://github.com/spring-projects/spring-integration/issues/2748

In many cases Spring Integration stack traces doesn't contain any
relations to end-user code.
Just because a target project code mostly contains only a configuration
for out-of-the-box components without any custom code.
When exception is thrown from such an out-of-the-box component, it is
hard from the stack trace to determine a configuration source for those
components.

* Add a logic into the `IntegrationObjectSupport` to obtain a its own
`BeanDefinition` from the `BeanFactory` to include a `resource` and
`source` (if any) into the `toString()` representation, as well as add
a new `getBeanDescription()` to get such an info at runtime
* The `toString()` is simply used by `this` reference in the message
for `MessagingException` thrown from the `IntegrationObjectSupport`
implementations
* Modify an exception message for the `MessageTransformingHandler` and
`MessageFilter` to make it based on `this`.
The `AbstractMessageHandler` already includes `this` into its exception
message
* Modify a `AbstractConsumerEndpointParser` and
`AbstractAmqpInboundAdapterParser` (as a sample) to include a `resource`
and `source` into a `MessageHandler` `BeanDefinition`.
* Include an `IntegrationFlow` `BeanDefinition` `resource`
(`@Configuration` class) and its bean method as a `source` into all
child beans declared during flow parsing in the `IntegrationFlowBeanPostProcessor`
* Add `IntegrationFlowRegistrationBuilder.setSource()` for manually
registered flows: there is no configuration parsing phase to extract
such an info from `BeanFactory`
* Propagate that `source` into all the child beans provided by the
`IntegrationFlow`
* Modify a `LambdaMessageProcessor` exception message to include a
method info in case of `InvocationTargetException`

* Do not cast explicitly for `ConfigurableListableBeanFactory` in the
`IntegrationObjectSupport` to avoid tests modifications for mocking
directly into `ConfigurableListableBeanFactory`.
Use `instanceof` instead in the `getBeanDescription()`

* * Fix Checkstyle issues

* * Fix `IntegrationGraphServer` and  `IntegrationMBeanExporter`
to rely on the `NamedComponent` for channel names instead of
always call `toString()` which is now much more than just a bean name
* Don't describe a `componentName` if it is the same as a `beanName`
* Check for parent `BeanDefinition` in the `IntegrationFlowBeanPostProcessor`
before calling its meta-info
* Fix tests according new `IntegrationObjectSupport.toString()` behavior
This commit is contained in:
Artem Bilan
2019-07-17 15:11:10 -04:00
committed by Gary Russell
parent ebb22c2ed4
commit c712416b63
25 changed files with 330 additions and 202 deletions

View File

@@ -25,6 +25,7 @@ import org.w3c.dom.Element;
import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
@@ -39,6 +40,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.1
*/
@@ -103,7 +105,7 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
builder.addConstructorArgReference(listenerContainerRef);
}
else {
BeanDefinition listenerContainerBeanDef = this.buildListenerContainer(element, parserContext);
BeanDefinition listenerContainerBeanDef = buildListenerContainer(element, parserContext);
builder.addConstructorArgValue(listenerContainerBeanDef);
}
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "message-converter");
@@ -117,11 +119,12 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "phase");
this.configureChannels(element, parserContext, builder);
configureChannels(element, parserContext, builder);
AbstractBeanDefinition adapterBeanDefinition = builder.getRawBeanDefinition();
adapterBeanDefinition.setResource(parserContext.getReaderContext().getResource());
adapterBeanDefinition.setSource(IntegrationNamespaceUtils.createElementDescription(element));
}
protected abstract void configureChannels(Element element, ParserContext parserContext, BeanDefinitionBuilder builder);
private BeanDefinition buildListenerContainer(Element element, ParserContext parserContext) {
if (!element.hasAttribute("queue-names")) {
parserContext.getReaderContext().error("If no 'listener-container' reference is provided, " +
@@ -163,7 +166,7 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
private void assertNoContainerAttributes(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
List<String> allContainerAttributes = new ArrayList<String>(Arrays.asList(CONTAINER_VALUE_ATTRIBUTES));
List<String> allContainerAttributes = new ArrayList<>(Arrays.asList(CONTAINER_VALUE_ATTRIBUTES));
allContainerAttributes.addAll(Arrays.asList(CONTAINER_REFERENCE_ATTRIBUTES));
for (String attributeName : allContainerAttributes) {
if (StringUtils.hasText(element.getAttribute(attributeName))) {
@@ -173,4 +176,7 @@ abstract class AbstractAmqpInboundAdapterParser extends AbstractSingleBeanDefini
}
}
protected abstract void configureChannels(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder);
}