17
votes

I searched for the below problem, but couldn't find an answer.

I want to use spring's conversion service by writing my custom converter that implements org.springframework.core.convert.converter.Converter.

Then i add my custom converter as below:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean id="StringToLoggerConverter" class="com.citi.spring.converter.LoggerConverter" />
       </list>
   </property>
</bean>

When doing the above my application initialization fails, because i am overriding bean conversionService and registering only my custom converter.

How can i not override the conversionService and only add my custom converter to the list of converters, at the same time keeping the existing ones?

Thanks in advance.

9

9 Answers

12
votes

For anyone who stumbles on this now via a google search or similar 2+ years after the question was originally posted, adding converters has been made much easier through Java Config: WebMvcConfigurerAdapter provides the addFormatters(FormatterRegistry) method that can be used to specify additional custom converters.

4
votes

While experimenting with different ways and even following spring source code in some i came across with an interesting thing.

The only way i found to use conversionService without overriding the existing converters with my custom ones was either to extend or re-implement the conversionService calling the super class's afterPropertiesSet() method to register the default converters and then add the custom ones.

But even if i was using that way, at runtime i was getting an exception that no converter was found for my specific types (e.g. from String to Logger).

That triggered my interest and i followed spring source code to find out why and i realized that spring was trying to find a custom converter registered with PropertyEditor. I am not sure why this is happening. I have to add here that my application is not using spring mvc and conversionService might somehow need to be registered and i didn't do it.

Finally, i solved the issue with registering a custom converter using Property editor. This documentation can be viewed as reference:

http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html

I would be very interested in knowing why Spring was not finding my registered custom converters in the conversionService's registry (or at least why spring was not looking at that registry to find the custom converters). Was i missing any configuration?

2
votes

You can also add it dinamically using addConverter method on your DefaultConversionService-ish class:

DefaultConversionService cs = new <YourClassThatInheritsFromDefaultConversionService or DefaultConversionService>();

cs.addConverter(new MyConverter());
2
votes

With spring > 4 it is no more necessary to implement an own derivation of the ConversionService. Initialize it in a @Configuration annotated class as follows:

@Configuration
public class ConversionServiceProvider
{
    @Autowired
    private MyConverterImpl myConverter;

    @Bean
    public ConversionService getConversionService()
    {
        ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
        bean.setConverters( getConverters() );
        bean.afterPropertiesSet();
        ConversionService object = bean.getObject();
        return object;
    }

    private Set<Converter<?, ?>> getConverters()
    {
        Set<Converter<?, ?>> converters = new HashSet<Converter<?, ?>>();

        converters.add( myConverter );
        // add here more custom converters, either as spring bean references or directly instantiated

        return converters;
    }
}
1
votes

Came across a very interesting way to do this in Stackoverflow - https://stackoverflow.com/a/12760579/204788

Using a feature called Collection merging, you can essentially do this:

<bean id="customConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" parent="conversionService">
    <property name="converters">
        <list merge="true">
            <bean id="StringToLoggerConverter" class="com.citi.spring.converter.LoggerConverter" />
       </list>
   </property>
</bean>
1
votes

It's enough to override the ConversionServiceFactoryBean.afterPropertiesSet() and set the ConversionService object to your converters there. Let your converters implement some interface that allows to set a ConversionService object, say ConversionServiceAware. The only problem is to get access to the registered converters, so you'll also have to override the 'setConverters()' method.

public class MyFormattingConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    private Set<?> cachedConverters = new LinkedHashSet<>();

    @Override
    public void setConverters(Set<?> converters) {
        super.setConverters(converters);
        this.cachedConverters = new LinkedHashSet<>(converters);

    }

    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();
        FormattingConversionService conversionService = getObject();
        for (Object converter : cachedConverters) {
            if (converter instanceof ConversionServiceAware) {
                ((ConversionServiceAware) converter).setConversionService(conversionService);
            }
        }
    }
}
1
votes

This variant works for me. If you use java configuration, you can add your converters to existing GenericConversionService

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html

Config methods may have an arbitrary name and any number of arguments; each of those arguments will be autowired with a matching bean in the Spring container. Bean property setter methods are effectively just a special case of such a general config method. Such config methods do not have to be public.

    @Configuration
    class MyConfig {

    @Autowired
    void conversionService(GenericConversionService genericConversionService) {
        genericConversionService.addConverter(String.class, UUID.class, UUID::fromString);
        genericConversionService.addConverter(String.class, DateTime.class, DateTime::parse);
        genericConversionService.addConverter(String.class, EnumState.class, EnumState::valueOf);
    }
}
1
votes

To resolve any circular dependencies, here the steps I followed (Spring Boot v5.2.1):

Register a simple conversionService

@Configuration
public class ConverterProvider {

    @Bean
    public ConversionService conversionService() {
        ConversionService conversionService = new GenericConversionService();
        return conversionService;
    }
}

Inject your custom converters

@Component
public class ConvertersInjection {

    @Autowired
    private GenericConversionService conversionService;

    @Autowired
    private void converters(Set<Converter> converters) {
        converters.forEach(conversionService::addConverter);
    }
}

The converter can even autowire your conversionService

@Component
public class PushNotificationConverter implements Converter<PushNotificationMessages.PushNotification, GCM> {
    @Lazy
    @Autowired
    private ConversionService conversionService;

    @Override
    public GCM convert(PushNotificationMessages.PushNotification source) {
        GCM gcm = new GCM();
        if (source.hasContent()) {
            PushNotificationMessages.PushNotification.Content content = source.getContent();
            if (content.hasData()) {
                conversionService.convert(content.getData(), gcm.getData().getClass());
            } else if (content.hasNotification()) {
                conversionService.convert(content.getNotification(), gcm.getNotification().getClass());
            }
        }
        return gcm;
    }
}
0
votes

write a custom conversionService

public class CustomerConversionServiceFactoryBean extends ConversionServiceFactoryBean {

    List<Converter<Object, Object>> converters = new ArrayList<>();

    public CustomerConversionServiceFactoryBean() {
        super();

    DefaultConversionService conversionservice = (DefaultConversionService)  super.getObject();

        for(int i=0;i<converters.size();i++){
            conversionservice.addConverter(converters.get(i));
        }
    }
}

then change your xml

<bean id="conversionService"
    class="CustomerConversionServiceFactoryBean" >
    <property name="converters" >
        <list>
            <bean class="CustomConverter"  />
        </list>
    </property>
</bean>

I think this will help you...