INT-3980: Don't Require @Component for Messaging

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

When we declare our components via `@Bean` (or `<bean>`) the requirements for
the `@Component` (`@MessageEndpoint`) looks redundant and sometimes even dangerous,
when we use `@ComponentScan`, too.

* Remove the `@Component` restriction logic from the `MessagingAnnotationPostProcessor`
* Ensure that tests pass

(We may consider to backport afterwards)

Add `requireComponentAnnotation` logic

* Introduce `spring.integration.messagingAnnotations.require.componentAnnotation` property for `spring.integration.properties` as `false` by default
* Add `MessagingAnnotationPostProcessor#setRequireComponentAnnotation()` to accept the value from the `spring.integration.messagingAnnotations.require.componentAnnotation`
 * Fix `integraton` typo everywhere
 * Document the change in the `What's New` and in the `configuration.adoc`
 * Ensure that logic works properly (no messaging endpoints populated) with the `spring.integration.messagingAnnotations.require.componentAnnotation = true` and "unannotated" `AnnotatedEndpoint2` in the `AnnotatedEndpointActivationTests`

This PR also fixes: https://jira.spring.io/browse/INT-3962

Fix issues according Travis report

Doc Polishing
This commit is contained in:
Artem Bilan
2016-04-06 18:48:47 -04:00
committed by Gary Russell
parent 40a9d28667
commit e5bf0187eb
12 changed files with 123 additions and 63 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 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.
@@ -363,8 +363,11 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
*/
private void registerMessagingAnnotationPostProcessors(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME)) {
String requireComponentAnnotationExpression =
IntegrationProperties.getExpressionFor(IntegrationProperties.REQUIRE_COMPONENT_ANNOTATION);
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(MessagingAnnotationPostProcessor.class)
.addPropertyValue("requireComponentAnnotation", requireComponentAnnotationExpression)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(IntegrationContextUtils.MESSAGING_ANNOTATION_POSTPROCESSOR_NAME,

View File

@@ -84,6 +84,8 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
private ConfigurableListableBeanFactory beanFactory;
private boolean requireComponentAnnotation;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.isAssignable(ConfigurableListableBeanFactory.class, beanFactory.getClass(),
@@ -91,6 +93,17 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
/**
*
* @param requireComponentAnnotation the {@code boolean} flag to indicate requirements for the
* {@link Component} annotation presentation for the messaging annotations.
* @since 4.3
* @see org.springframework.integration.context.IntegrationProperties#REQUIRE_COMPONENT_ANNOTATION
*/
public void setRequireComponentAnnotation(boolean requireComponentAnnotation) {
this.requireComponentAnnotation = requireComponentAnnotation;
}
protected ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
@@ -134,7 +147,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
final Class<?> beanClass = this.getBeanClass(bean);
if (AnnotationUtils.findAnnotation(beanClass, Component.class) == null) {
if (this.requireComponentAnnotation && AnnotationUtils.findAnnotation(beanClass, Component.class) == null) {
// we only post-process stereotype components
return bean;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 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.
@@ -33,7 +33,7 @@ import org.springframework.core.io.support.ResourcePatternResolver;
*/
public final class IntegrationProperties {
public static final String INTEGRATION_PROPERTIES_PREFIX = "spring.integraton.";
public static final String INTEGRATION_PROPERTIES_PREFIX = "spring.integration.";
/**
* Specifies whether to allow create automatically {@link org.springframework.integration.channel.DirectChannel}
@@ -66,6 +66,11 @@ public final class IntegrationProperties {
*/
public static final String THROW_EXCEPTION_ON_LATE_REPLY = INTEGRATION_PROPERTIES_PREFIX + "messagingTemplate.throwExceptionOnLateReply";
/**
* Specifies the value of {@link org.springframework.integration.config.annotation.MessagingAnnotationPostProcessor#requireComponentAnnotation}.
*/
public static final String REQUIRE_COMPONENT_ANNOTATION = INTEGRATION_PROPERTIES_PREFIX + "messagingAnnotations.require.componentAnnotation";
private static Properties defaults;
static {

View File

@@ -1,5 +1,6 @@
spring.integraton.channels.autoCreate=true
spring.integraton.channels.maxUnicastSubscribers=0x7fffffff
spring.integraton.channels.maxBroadcastSubscribers=0x7fffffff
spring.integraton.taskScheduler.poolSize=10
spring.integraton.messagingTemplate.throwExceptionOnLateReply=false
spring.integration.channels.autoCreate=true
spring.integration.channels.maxUnicastSubscribers=0x7fffffff
spring.integration.channels.maxBroadcastSubscribers=0x7fffffff
spring.integration.taskScheduler.poolSize=10
spring.integration.messagingTemplate.throwExceptionOnLateReply=false
spring.integration.messagingAnnotations.require.componentAnnotation=false

View File

@@ -1,17 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<annotation-config/>
<annotation-config/> <!-- Second declaration should not be a problem - see INT-3445 -->
<annotation-config/> <!-- Second declaration should not be a problem - see INT-3445 -->
<beans:bean class="org.springframework.integration.config.annotation.AnnotatedEndpointActivationTests.AnnotatedEndpoint"/>
<util:properties id="integrationGlobalProperties">
<beans:prop key="spring.integration.messagingAnnotations.require.componentAnnotation">true</beans:prop>
</util:properties>
<beans:bean id="annotatedEndpoint"
class="org.springframework.integration.config.annotation.AnnotatedEndpointActivationTests.AnnotatedEndpoint"/>
<beans:bean id="annotatedEndpoint2"
class="org.springframework.integration.config.annotation.AnnotatedEndpointActivationTests.AnnotatedEndpoint2"/>
<channel id="input"/>

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.config.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -70,11 +71,6 @@ public class AnnotatedEndpointActivationTests {
count = 0;
}
@Test
public void configCheck() {
assertTrue(true);
}
@Test
public void sendAndReceive() {
this.input.send(new GenericMessage<String>("foo"));
@@ -82,6 +78,9 @@ public class AnnotatedEndpointActivationTests {
assertNotNull(message);
assertEquals("foo: 1", message.getPayload());
assertEquals(1, count);
assertTrue(this.applicationContext.containsBean("annotatedEndpoint.process.serviceActivator"));
assertFalse(this.applicationContext.containsBean("annotatedEndpoint2.process.serviceActivator"));
}
@Test
@@ -130,4 +129,15 @@ public class AnnotatedEndpointActivationTests {
}
private static class AnnotatedEndpoint2 {
@ServiceActivator(inputChannel = "input", outputChannel = "output")
public String process(String message) {
count++;
return message + ": " + count;
}
}
}

View File

@@ -8,8 +8,8 @@
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:properties id="integrationGlobalProperties">
<prop key="spring.integraton.channels.maxUnicastSubscribers">456</prop>
<prop key="spring.integraton.channels.maxBroadcastSubscribers">789</prop>
<prop key="spring.integration.channels.maxUnicastSubscribers">456</prop>
<prop key="spring.integration.channels.maxBroadcastSubscribers">789</prop>
</util:properties>
<import resource="DispatcherMaxSubscribersDefaultConfigurationTests-context.xml" />

View File

@@ -74,7 +74,6 @@ import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.annotation.Publisher;
@@ -137,8 +136,8 @@ import reactor.rx.Streams;
* @since 4.0
*/
@ContextConfiguration(loader = AnnotationConfigContextLoader.class,
classes = {EnableIntegrationTests.ContextConfiguration.class,
EnableIntegrationTests.ContextConfiguration2.class})
classes = { EnableIntegrationTests.ContextConfiguration.class,
EnableIntegrationTests.ContextConfiguration2.class })
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class EnableIntegrationTests {
@@ -414,14 +413,14 @@ public class EnableIntegrationTests {
@DirtiesContext
public void testChangePatterns() {
try {
this.configurer.setComponentNamePatterns(new String[] {"*"});
this.configurer.setComponentNamePatterns(new String[] { "*" });
fail("ExpectedException");
}
catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("cannot be changed"));
}
this.configurer.stop();
this.configurer.setComponentNamePatterns(new String[] {"*"});
this.configurer.setComponentNamePatterns(new String[] { "*" });
assertEquals("*", TestUtils.getPropertyValue(this.configurer, "componentNamePatterns", String[].class)[0]);
}
@@ -665,7 +664,7 @@ public class EnableIntegrationTests {
@IntegrationComponentScan
@EnableIntegration
// INT-3853 @PropertySource("classpath:org/springframework/integration/configuration/EnableIntegrationTests.properties")
@EnableMessageHistory({"input", "publishedChannel", "annotationTestService*"})
@EnableMessageHistory({ "input", "publishedChannel", "annotationTestService*" })
public static class ContextConfiguration {
@Bean
@@ -1045,6 +1044,11 @@ public class EnableIntegrationTests {
return new DirectChannel();
}
@Bean
public AnnotationTestService annotationTestService() {
return new AnnotationTestServiceImpl();
}
}
@Configuration
@@ -1112,7 +1116,6 @@ public class EnableIntegrationTests {
}
@MessageEndpoint("annotationTestService")
public static class AnnotationTestServiceImpl implements Lifecycle, AnnotationTestService {
private final AtomicInteger counter = new AtomicInteger();
@@ -1239,7 +1242,7 @@ public class EnableIntegrationTests {
@Override
@MyServiceActivator1(inputChannel = "annInput1", autoStartup = "true",
adviceChain = {"annAdvice1"}, poller = @Poller(fixedRate = "2000"))
adviceChain = { "annAdvice1" }, poller = @Poller(fixedRate = "2000"))
public Integer annCount1() {
return 0;
}
@@ -1324,7 +1327,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MessagingGateway(defaultRequestChannel = "gatewayChannel",
defaultRequestTimeout = "${default.request.timeout:12300}", defaultReplyTimeout = "#{13400}",
@@ -1344,13 +1347,13 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@ServiceActivator(autoStartup = "false",
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
adviceChain = {"annAdvice"},
adviceChain = { "annAdvice" },
poller = @Poller(fixedDelay = "1000"))
public @interface MyServiceActivator {
@@ -1358,16 +1361,16 @@ public class EnableIntegrationTests {
String outputChannel() default "";
String[] adviceChain() default {};
String[] adviceChain() default { };
String autoStartup() default "";
String phase() default "";
Poller[] poller() default {};
Poller[] poller() default { };
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator
public @interface MyServiceActivator1 {
@@ -1376,16 +1379,16 @@ public class EnableIntegrationTests {
String outputChannel() default "";
String[] adviceChain() default {};
String[] adviceChain() default { };
String autoStartup() default "";
String phase() default "";
Poller[] poller() default {};
Poller[] poller() default { };
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator1
public @interface MyServiceActivator2 {
@@ -1394,7 +1397,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator2
public @interface MyServiceActivator3 {
@@ -1403,7 +1406,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator3(inputChannel = "annInput3")
public @interface MyServiceActivator4 {
@@ -1412,7 +1415,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator4
public @interface MyServiceActivator5 {
@@ -1423,7 +1426,7 @@ public class EnableIntegrationTests {
// Test prevent infinite recursion
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator5
public @interface MyServiceActivator6 {
@@ -1432,7 +1435,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator8
public @interface MyServiceActivator7 {
@@ -1441,7 +1444,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyServiceActivator7
public @interface MyServiceActivator8 {
@@ -1457,7 +1460,7 @@ public class EnableIntegrationTests {
phase = "23",
inputChannel = "annInput",
outputChannel = "annOutput",
adviceChain = {"annAdvice"},
adviceChain = { "annAdvice" },
poller = @Poller(fixedDelay = "1000"))
public @interface MyServiceActivatorNoLocalAtts {
@@ -1487,7 +1490,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default {};
Poller[] poller() default { };
}
@Target(ElementType.METHOD)
@@ -1508,7 +1511,7 @@ public class EnableIntegrationTests {
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@InboundChannelAdapter(value = "counterChannel", autoStartup = "false", phase = "23")
public @interface MyInboundChannelAdapter {
@@ -1519,7 +1522,7 @@ public class EnableIntegrationTests {
String phase() default "";
Poller[] poller() default {};
Poller[] poller() default { };
}

View File

@@ -511,9 +511,9 @@ public class MethodInvokingSplitterTests {
return messages;
}
public List<AbstractIntegrationMessageBuilder> messageToMessageBuilderList(Message<?> input) {
public List<AbstractIntegrationMessageBuilder<String>> messageToMessageBuilderList(Message<?> input) {
String[] strings = input.getPayload().toString().split("\\.");
List<AbstractIntegrationMessageBuilder> messageBuilders = new ArrayList<>();
List<AbstractIntegrationMessageBuilder<String>> messageBuilders = new ArrayList<>();
for (String s : strings) {
MessageBuilder<String> builder = MessageBuilder.withPayload(s)
.setHeader("foo", "bar");

View File

@@ -1,5 +1,5 @@
#spring.integraton.channels.autoCreate=false
#spring.integraton.channels.maxUnicastSubscribers=1
#spring.integraton.channels.maxBroadcastSubscribers=1
spring.integraton.taskScheduler.poolSize=20
spring.integraton.messagingTemplate.throwExceptionOnLateReply=true
#spring.integration.channels.autoCreate=false
#spring.integration.channels.maxUnicastSubscribers=1
#spring.integration.channels.maxBroadcastSubscribers=1
spring.integration.taskScheduler.poolSize=20
spring.integration.messagingTemplate.throwExceptionOnLateReply=true

View File

@@ -193,15 +193,17 @@ Certain global framework properties can be overridden by providing a properties
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], but here are the current default values:
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],
but here are the current default values:
[source]
----
spring.integraton.channels.autoCreate=true <1>
spring.integraton.channels.maxUnicastSubscribers=0x7fffffff <2>
spring.integraton.channels.maxBroadcastSubscribers=0x7fffffff <3>
spring.integraton.taskScheduler.poolSize=10 <4>
spring.integraton.messagingTemplate.throwExceptionOnLateReply=false <5>
spring.integration.channels.autoCreate=true <1>
spring.integration.channels.maxUnicastSubscribers=0x7fffffff <2>
spring.integration.channels.maxBroadcastSubscribers=0x7fffffff <3>
spring.integration.taskScheduler.poolSize=10 <4>
spring.integration.messagingTemplate.throwExceptionOnLateReply=false <5>
spring.integration.messagingAnnotations.require.componentAnnotation=false <6>
----
<1> When true, `input-channel` s will be automatically declared as `DirectChannel` s when not explicitly found in the
@@ -217,12 +219,17 @@ This can be overridden on individual channels with the `max-subscribers` attribu
<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 will throw an exception, when the gateway is not
<5> When `true`, messages that arrive at a gateway reply channel will throw an exception, when the gateway is not
expecting a reply - because the sending thread has timed out, or already received a reply.
<6> When `true`, Messaging Annotation Support (<<annotations>>) requires a declaration of the
`@MessageEndpoint` (or any other `@Component`) annotation on the class level.
These properties can be overridden by adding a file `/META-INF/spring.integration.properties` to the classpath.
It is not necessary to provide all the properties, just those that you want to override.
NOTE: In versions prior to _4.3_, these property names had a typographical error (`...integraton...`); they have now been
corrected (`...integration...`).
[[annotations]]
=== Annotation Support

View File

@@ -40,6 +40,14 @@ If you have such configuration, simply remove the `reply-channel`.
An option to make the Service Asynchronous has been added.
See <<async-service-activator>> for more information.
===== Messaging Annotation Support changes
The Messaging Annotation Support doesn't require any more `@MessageEndpoint` (or any other `@Component`) annotation
declaration on the class level.
To restore the previous behaviour specify the `spring.integration.messagingAnnotations.require.componentAnnotation` of
`spring.integration.properties` as `true`.
See <<global-properties>> and <<annotations>> for more information.
==== Mail Changes
The customizable `userFlag` added in 4.2.2 to provide customization of the flag used to denote that the mail has been