Spring autowiring by parameter name does not work for FtpMessageHandlerSpec - spring

I am unable to autowire org.springframework.integration.ftp.dsl.FtpMessageHandlerSpec by using bean name + parameter name that is same as bean name. I believe that Spring should be able to resolve dependencies when there are multiple beans of same type in case that bean name and target parameter name are the same.
I have config like this:
#Configuration
class cfg {
#Bean
Object ftpMessageHandlerSpecUser(
FtpMessageHandlerSpec ftpMessageHandlerSpecA,
FtpMessageHandlerSpec ftpMessageHandlerSpecB
) {
return null;
}
#Bean
FtpMessageHandlerSpec ftpMessageHandlerSpecA() {
return outboundAdapter(() -> null);
}
#Bean
FtpMessageHandlerSpec ftpMessageHandlerSpecB() {
return outboundAdapter(() -> null);
}
}
When I try to run it a get following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method ftpMessageHandlerSpecUser in com.example.demo.cfg required a single bean, but 2 were found:
- &ftpMessageHandlerSpecA: defined by method 'ftpMessageHandlerSpecA' in class path resource [com/example/demo/cfg.class]
- &ftpMessageHandlerSpecB: defined by method 'ftpMessageHandlerSpecB' in class path resource [com/example/demo/cfg.class]
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
When i add #Qualifier to method parameters it works:
#Configuration
class cfg {
#Bean
Object ftpMessageHandlerSpecUser(
#Qualifier("ftpMessageHandlerSpecA") FtpMessageHandlerSpec ftpMessageHandlerSpecA,
#Qualifier("ftpMessageHandlerSpecB") FtpMessageHandlerSpec ftpMessageHandlerSpecB
) {
return null;
}
#Bean
FtpMessageHandlerSpec ftpMessageHandlerSpecA() {
return outboundAdapter(() -> null);
}
#Bean
FtpMessageHandlerSpec ftpMessageHandlerSpecB() {
return outboundAdapter(() -> null);
}
}
This works:
#Configuration
class cfg2 {
#Bean
Object foo(
String beanA,
String beanB
) {
return null;
}
#Bean
String beanA() {
return "beanA";
}
#Bean
String beanB() {
return "beanB";
}
}
Why it does not work with FtpMessageHandlerSpec from spring integration?

DSL *Specs are FactoryBeans for the underlying SI components; hence the leading & on the bean name, so you need a #Qualifier.
Exactly what are you trying to achieve by injecting these anyway? They are simply building blocks for integration flows.

Related

Spring #Bean(name ="name") vs #Bean #Qualifier("name")

Is there any differences between the following 2 bean declaration?
#Bean(name = "bean1")
public A getA() {
return new A();
}
#Bean
#Qualifier("bean1")
public A getA() {
return new A();
}
Both can be autowired using #Qualifier
#Autowire
public void test(#Qualifier("bean1") A a) {
...
}
With value() you don't have to specify attribute name, like #Qualifier("bean1"). Attribute name() reference the same value as value() because of custom annotation #AliasFor(..) from Spring, therefore they are just different names with the same behavior.
You can use
#Autowire
public void test(A bean1) {
...
}
if you use
#Bean(name = "bean1")
not with
#Bean
#Qualifier("bean1")
The first part is fundamentally the same, the second part is what you basically need when two or more beans of same type exist. The first part is just the preference one might have.
You can have multiple beans with same Qualifier name but bean name in spring application context needs to be unique
#Bean
#Qualifier("qualifier1")
Foo foo()
{
return new Foo();
}
#Bean
#Qualifier("qualifier1")
Bar bar()
{
return new Bar();
}
The above code is acceptable. but in the case of bean, it is not.

#Bean-method providing a list is ignored, if there is a #Bean-method proving a single bean

I have a configuration providing a single bean and a configuration providing a list of beans. All these beans have the same type.
When I start up an application context with these configurations, I see that an autowired list of the bean type only contains the single bean. I want it to include all beans of that type. I use Spring 5.2.0.
I have boiled it down to one configuration: if I provide a single bean and a list of beans, only the single bean will be used.
This is reproduced in the following test. It fails, because the list only contains "A" and "D" (which shows it did not autowire the list of beans):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = { TestConfiguration.class })
class AutowiringListsTest {
#Autowired
private List<TestBean> testBeanList;
#Test
void testThatBothConfigurationsContributeToBeanList() {
final List<String> idList = testBeanList.stream().map(TestBean::getId).sorted().collect(Collectors.toList());
assertThat(idList, hasItems("A", "B", "C", "D"));
}
#Configuration
public static class TestConfiguration {
#Bean
public TestBean someBean() {
return new TestBean("A");
}
#Bean
public List<TestBean> someMoreBeans() {
return Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBean anotherBean() {
return new TestBean("D");
}
}
public static class TestBean {
private final String id;
public TestBean(final String id) {
this.id = id;
}
private String getId() {
return id;
}
}
}
I want to get this to run so that multiple modules can provide beans of a certain type.
Some modules want to provide multiple beans and their number depends on a property.
Some modules will always provide one bean.
The module using the beans (autowiring them as list) should autowire all beans.
How can I get this to run? In what scenario does Spring's behavior make sense?
I can work around the issue by introducing a TestBeanFactory. Each configuration that wants to contribute to the list of TestBeans instead provides a factory.
#Configuration
public static class TestConfiguration {
/** Implemented once in the configuration that defines <code>TestBean</code>. */
#Bean
public List<TestBean> testBeansFromFactory(Collection<TestBeanFactory> factories) {
return factories.stream().map(TestBeanFactory::createTestBeans).flatMap(Collection::stream)
.collect(toList());
}
// Further methods can be defined in various configurations that want to add to the list of TestBeans.
#Bean
public TestBeanFactory someBean() {
return () -> Arrays.asList(new TestBean("A"));
}
#Bean
public TestBeanFactory someMoreBeans() {
return () -> Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBeanFactory anotherBean() {
return () -> Arrays.asList(new TestBean("D"));
}
}
public static class TestBean { ... }
public static interface TestBeanFactory {
public Collection<TestBean> createTestBeans();
}
That works and is only slightly more code.
M.Deinum makes a point in the comments for Spring's behavior being consistent:
As you defined a bean of type List it will be used. Autowiring is based on type, it will try to detect the bean of the certain type. The collection (and map as well) is a special one looking up all dependencies of the given type.

How to declare an optional #Bean in Spring?

I want to provide an optional #Bean in my #Configuration file like:
#Bean
public Type method(Type dependency) {
// TODO
}
when dependency can't be found, the method should not be called.
How to do that?
You need to use ConditionalOnClass If using SpringBoot and Conditional in Spring since 4.0 See If using Spring
Example of SpringBoot :-
#Bean
#ConditionalOnClass(value=com.mypack.Type.class)
public Type method() {
......
return ...
}
Now the method() will be called only when com.mypack.Type.class is in classpath.
In addition to the accepted answer, you have to check if the dependency is initialized before calling any method which requires that dependency.
#Autowired(required = false)
Type dependency;
public Type methodWhichRequiresTheBean() {
...
}
public Type someOtherMethod() {
if(dependency != null) { //Check if dependency initialized
methodWhichRequiresTheBean();
}
}

Bean reference overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy159]

I am a beginner in developing web services and I have a jaxrs web service which has the folllowing config :
#Configuration
#ComponentScan("com.example.service")
#ComponentScan("com.example.services")
#ImportResource({
"classpath:/META-INF/cxf/cxf.xml",
"classpath:/META-INF/cxf/cxf-servlet.xml"
})
public class AppConfig {
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public Server jaxRsServer() {
//Define swagger feature
Swagger2Feature feature = new Swagger2Feature();
//REST Factory with all services,providers and features
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(), JAXRSServerFactoryBean.class);
factory.setServiceBeans(Arrays.asList(baseRestService(), materialsRestService(), batchRestService(), billingRestService(), locationRestService(), customerRestService(), equipmentRestService(), projectRestService(), reservationRestService(), waferRestService()));
factory.setAddress(factory.getAddress());
factory.setProviders(Arrays.asList(jsonProvider(), authenticationService()));
factory.getFeatures().add(feature);
return factory.create();
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
#Bean
public JacksonJsonProvider jsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public AuthenticationService authenticationService() {
return new AuthenticationService();
}
**all other beans**
Recently I started getting the following exception:
java.lang.IllegalStateException: #Bean method AppConfig.materialsRestService called as a bean reference for type [com.phoenixbv.rs.MaterialsRestService] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy159]. Overriding bean of same name declared in: com.example.config.AppConfig
I would appreciate any help !
I was able to solve the problem by creating Interfaces to the service classes and the injecting the interfaces into the factory.

Programmatic Bean registration

i would like to understand what is the programmatic equivalent of a #Bean annotated bean registration
Lets say i have a class like this:
#Configuration
public class SimpleConfiguration {
#Bean
public BigDecimal aDecimal( String example ) {
return new BigDecimal( example );
}
}
here is what i think happens here:
somehow spring register this method as a factory for a bean named
"aDecimal" of type BigDecimal, with a dependency on a bean of type
String
at some point this method will be called with the right bean as
parameter and the result will be the instance associated to the
"aDecimal" bean.
If i wanted to do the same with something like this:
#Configuration
public class DynamicConfiguration {
public void registerDefinition() {
/* i know it can't be like this but i hope it's clear what i mean */
register( "aDecimal", (example) -> aDecimal( example ) );
}
public BigDecimal aDecimal( String example ) {
/* this could be any kind of complex bean creation method */
return new BigDecimal( example );
}
}
what would be the right way to achieve this result?
i already researched a bit about this, and i found for example
How do I create beans programmatically in Spring Boot?
but this kind of registration doesn't seem as powerful as the annotation, and let's spring instatiate the bean, i want the bean to be instatied by a provided method
How to programmatically create bean definition with injected properties?
and this is missing the ability to call a method with injected bean parameters.
the reason i want to do this, is that i have some configuration classes that hold a lot of the same kind of beans with different qualifiers, based on a configuration file.
now every time the configuration file expands, i need to add new beans and configurations ( many of these are spring SessionFactories and SpringIntegration flows so i need these things to be spring beans )
You need to consider to use IntegrationFlowContext:
#Autowired
private IntegrationFlowContext integrationFlowContext;
...
IntegrationFlow myFlow = f -> ...;
BeanFactoryHandler additionalBean = new BeanFactoryHandler();
IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(myFlow)
.addBean(additionalBean)
.register();
It provides for you hooks to register additional beans at runtime, not only IntegrationFlow structure.
I found the way to solve my problem, it all happens in the BeanDefinition "phase" this way everything is managed by spring and works exactly the same as as a #Bean annotated method with injected parameters, it also cleanly bridge between annotated and programmatically registered beans.
here is what i did
#Configuration
#RunWith( SpringJUnit4ClassRunner.class )
#ContextConfiguration( classes = { TestSpringDynamicConfiguration.class } )
public class TestSpringDynamicConfiguration implements BeanDefinitionRegistryPostProcessor {
#Autowired
#Qualifier( "qualified" )
private String dynamicString;
#Bean
public Integer zero() {
return 0;
}
public String zeroString( Integer aInteger ) {
return aInteger.toString();
}
#Override
public void postProcessBeanDefinitionRegistry( final BeanDefinitionRegistry registry ) throws BeansException {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setAutowireMode( GenericBeanDefinition.AUTOWIRE_CONSTRUCTOR );
beanDefinition.setScope( BeanDefinition.SCOPE_SINGLETON );
beanDefinition.setFactoryBeanName( "testSpringDynamicConfiguration" );
beanDefinition.setFactoryMethodName( "zeroString" );
registry.registerBeanDefinition( "qualified", beanDefinition );
}
#Override public void postProcessBeanFactory( final ConfigurableListableBeanFactory beanFactory ) throws BeansException { }
#Test
public void testDynamicConfiguration() throws Exception {
assertThat( dynamicString, is( "0" ) );
}
}

Resources