2

I am wondering if there is a possibility to put spring beans from a component scan into a list to inject the list to a property?

I am using Spring 3.0.x and Java 6 (1.6).

What I have tried so far is the following:

<util:list id="converterList">
    <context:component-scan base-package="com.company.convert"/>
</util:list>
<!-- configure custom b2bde converters -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <ref local="converterList"/>
        </list>
    </property>
</bean>

But I get following exception:

SEVERE: Exception sending context initialized event to listener instance of class de.hybris.platform.spring.HybrisContextLoaderListener
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Incorrect usage of element 'context:component-scan' in a nested
 manner. This tag cannot be used nested inside <property>.
Offending resource: ServletContext resource [/WEB-INF/my-servlet.xml]

        at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
        at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:284)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseNestedCustomElement(BeanDefinitionParserDelegate.java:1390)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parsePropertySubElement(BeanDefinitionParserDelegate.java:956)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCollectionElements(BeanDefinitionParserDelegate.java:1140)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseListElement(BeanDefinitionParserDelegate.java:1116)
        at org.springframework.beans.factory.xml.UtilNamespaceHandler$ListBeanDefinitionParser.doParse(UtilNamespaceHandler.java:123)
        at org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal(AbstractSingleBeanDefinitionParser.java:85)
        at org.springframework.beans.factory.xml.AbstractBeanDefinitionParser.parse(AbstractBeanDefinitionParser.java:59)
        at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1335)
        at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1325)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:135)
        at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
        at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
        at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
        at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:124)
        at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:93)
        at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
        at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:467)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:397)

Do I missing a point or is it not really possible to scan beans, put them into a list and use this list in a property?

Best Regards, Walter

Walter
  • 225
  • 5
  • 14

3 Answers3

2

You probably won't ever be able to do that in XML, but using Java Config (a Spring 3 feature), it should be easy enough, using the org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.

Updated following comments

Or continue to use XML <context:component-scan base-package="com.company.convert"/> to get the beans registered, then in the Java Config, use org.springframework.beans.factory.ListableBeanFactory.getBeansOfType(Class<T>) (from the wired-in ApplicationContext object) to get them all and call the conversionService's setter.

artbristol
  • 32,010
  • 5
  • 70
  • 103
  • hmm, that would imply that I have to refactor our whole xml config or is there an approach to be able to use xml config and java config in conjunction with each other? – Walter Apr 08 '11 at 09:20
  • I'm pretty sure you can mix and match the two - see section 3.11.3.2 'Combining Java and XML configuration' – artbristol Apr 08 '11 at 09:32
  • I am trying to implement the config with classpathscanningcandidatecomponentprovider. Now I have bean definitions and I miss the link to instantiate these beans correctly and add it to the list. Is there a way to instantiate a bean from the bean definition without implementing to much by myself? – Walter Apr 08 '11 at 09:57
  • You might be better to continue using the XML to get the beans registered, then in the Java bean config, use org.springframework.beans.factory.ListableBeanFactory.getBeansOfType(Class) to get them all and call the conversionService's setter – artbristol Apr 08 '11 at 10:09
  • thanks. that was the right direction. cause applicaton contexts is of interface listablebeanfactory I wired the application context and got all converters from it and afterwards I converted it into a set. – Walter Apr 08 '11 at 12:29
2

What defently works is someting like this:

Interface Marker{};

@Service
class ServiceA implements Marker {...}

@Service
class ServiceB implements Marker {...}

@Service
class MyService implements {

  @Resource
  private List<Marker> allMarkerImplementations;

}

You can do the same with @Qualifyer instead of Marker Interfaces.

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface InterestingService {}

@InterestingService
@Service 
class ServiceA {...}

@InterestingService
@Service
class ServiceB {...}

@Service
class MyService implements {

  @Resource
  @InterestingService
  private List<Object> allInterestingServices;

}

This techique is helpfull if you need some special beans, if you need ALL, then I would look for an other solution.

Ralph
  • 118,862
  • 56
  • 287
  • 383
  • I understand the marker interface approach.I am than able to use the property that collected all implementations of the marker interface in xml to set it as converter list? – Walter Apr 08 '11 at 09:18
  • It is an (not 100%) xml free solution. I thought: if you use component scann, than you use all the other "modern" Spring stuff and do not need the xml. – Ralph Apr 08 '11 at 09:26
2

to finalize it I implemented following approach:

spring xml config:

<!-- java config provides the conversionService factory bean -->
<bean name="myCompanyJavaConfig" class="com.company.config.MyCompanySpringConfig"/> 

<!-- scans all converters which will be picked up by the MyCompanySpringConfig and will be registers as list to conversion service-->
<context:component-scan base-package="com.company.convert"/>


<!-- activates annotation driven binding -->
<!-- the conversion service is registered by the java config MyCompanySpringConfig -->
<mvc:annotation-driven conversion-service="conversionService"  validator="validator"/>

implementation of the java config:

package com.company.config;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ConversionServiceFactoryBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;

@Configuration
public class MyCompanySpringConfig
{

    @Autowired
    private ApplicationContext applicationContext;

    public @Bean
    FactoryBean<ConversionService> conversionService()
    {
        ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(converters());
        return conversionServiceFactoryBean;
    }

    public @Bean
    Set<Converter> converters()
    {
        Map<String, Converter> converterBeans = applicationContext.getBeansOfType(Converter.class);
        return new LinkedHashSet<Converter>(converterBeans.values());
    }
}

if it is the perfect way? I don't know ;-) but what I know is that it works now.

Thanks to all replies.

Best Regards, Walter

Walter
  • 225
  • 5
  • 14