Skip to content

Default MessageChannel bean for @ServiceActivator missing! #3111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
membersound opened this issue Nov 20, 2019 · 6 comments · Fixed by #3142
Closed

Default MessageChannel bean for @ServiceActivator missing! #3111

membersound opened this issue Nov 20, 2019 · 6 comments · Fixed by #3142

Comments

@membersound
Copy link

membersound commented Nov 20, 2019

The following code use do work up to spring-boot 2.1.10. But since 2.2.x the following code does not find the MessageChannel bean anymore:

@MessageEndpoint
public class ExampleMessageEndpoint {
        //creates a DirectChannel named "exampleChannel" implicit
	@ServiceActivator(inputChannel = "exampleChannel")
	public String example(String example) {
		//...
	}
}

@Configuration
@EnableIntegration
public class ExampleConfiguration {
	@Bean
	public TcpConnectionFactoryFactoryBean factory() throws Exception {
		TcpConnectionFactoryFactoryBean f = new TcpConnectionFactoryFactoryBean();
		f.setType("server");
		f.setPort(port);
		f.setUsingNio(true);
		//...
		return f;
	}

	//also @Qualifier("exampleChannel") does not work
	@Bean
	public TcpInboundGateway gateway(TcpConnectionFactoryFactoryBean f, MessageChannel c) throws Exception {
		TcpInboundGateway g = new TcpInboundGateway();
		g.setConnectionFactory(f.getObject());
		g.setRequestChannel(c);
		return g;
	}
}

Error:

Parameter 1 of method gateway in ExampleConfiguration required a single bean, but 2 were found:
	- nullChannel: defined in null
	- errorChannel: defined in null

Of course this could be solved by simply adding a:

	@Bean
	public DirectChannel exampleChannel() {
		return new DirectChannel();
	}

But as documented, I would consider this a bug.

@artembilan
Copy link
Member

Well, it even doesn't work without a @Qualifier("exampleChannel") in Boot 2.1.10:

Parameter 0 of method testBean in org.springframework.integration.gh3111.Gh3111Application required a single bean, but 3 were found:
	- nullChannel: defined in null
	- errorChannel: defined in null
	- exampleChannel: a programmatically registered singleton

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

But that's indeed not the point.
Technically it should not work even in that older version because we create a bean for that implicit during parsing @ServiceActivator in the BeanPostProcessor.
I think in the latest Spring Framework version the dependency injection candidate is determined on bean definition parsing phase, not later as it was before.
But that's my guess: I'm debugging both versions now to determine a difference.
Although I'm not sure that there is something we can do unless really document that implicit channels are not autowiring candidates.

BTW, what doc do you claim in your topic, please?

@artembilan artembilan added status: waiting-for-reporter Needs a feedback from the reporter status: waiting-for-triage The issue need to be evaluated and its future decided labels Nov 20, 2019
@artembilan
Copy link
Member

Well, I found the reason why we fail now: #2769

We defer endpoint creation (together with its implicit channel) to the phase when context is ready already.

So, this a behavior change for your use-case to make working some other use-cases which results otherwise much worse than yours...

I need to think more about this, but I'm afraid we would need to postpone the final decision to the next 5.3 version.

One of the idea is still parse messaging annotations for potential channel creation similar way we did before, but really defer an endpoint creation like it is right now.

Any other thoughts?

Thanks

@membersound
Copy link
Author

membersound commented Nov 20, 2019

Or just remove the "implicit direct channel creation" feature and force the user to provide a directchannel directly as a bean?

Because as of now, the feature is suggested here:
https://docs.spring.io/spring-integration/api/org/springframework/integration/annotation/ServiceActivator.html

Specify the channel from which this service activator will consume messages. If the channel does not exist, a DirectChannel with this name will be registered in the application context.

@artembilan
Copy link
Member

That's correct, but as you see that doc doesn't say that this channel is good for autowiring somewhere else.

I don't think it is good to remove such a feature at all: it is there from day first. So, we should try to pursue its goal.

@artembilan
Copy link
Member

@membersound ,

see my comment in the related issue: #3130 (comment)

May that @Lazy can do the trick for your use-case as well:

@Bean
public TcpInboundGateway gateway(TcpConnectionFactoryFactoryBean f, @Qualifier("exampleChannel") @Lazy MessageChannel c) throws Exception {

BTW, you don't need to inject a FactoryBean. You simply can use its target object instead.
So, you won't need that throws and f.getObject(). That's the way how we really can overcome a limitation with bean method reference.
I mean try code like this:

@Bean
public TcpInboundGateway gateway(ConnectionFactory f, @Qualifier("exampleChannel") @Lazy MessageChannel c) {

artembilan added a commit to artembilan/spring-integration that referenced this issue Jan 14, 2020
Fixes spring-projects#3111
Fixes spring-projects#3130

* Fix some typos in docs
* Ensure in tests that announced `@Lazy` works as expected
garyrussell added a commit that referenced this issue Jan 15, 2020
* GH-3111: Document @lazy for messaging annotations

Fixes #3111
Fixes #3130

* Fix some typos in docs
* Ensure in tests that announced `@Lazy` works as expected

* Doc Polishing

Co-authored-by: Gary Russell <[email protected]>
@membersound
Copy link
Author

membersound commented Jan 16, 2020

@artembilan thanks for your hints. Both using @Lazy as well as directly injecting the factory works. Though I had to inject AbstractConnectionFactory instead of ConnectionFactory for TcpInboundGateway.setConnectionFactory(f)).

@garyrussell garyrussell added type: documentation and removed status: waiting-for-reporter Needs a feedback from the reporter status: waiting-for-triage The issue need to be evaluated and its future decided labels Dec 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants